DEV Community

Cover image for Conditional wrapping in React
Chris Bongers
Chris Bongers

Posted on • Edited on • Originally published at daily-dev-tips.com

Conditional wrapping in React

This is something you do not always need, but I wrote this article for those looking for it.

Sometimes we might have a generic element, a specific component that renders inside a modal.
When a specific flag is set, the component should get a parent wrapper to display it in a different variant.

We could use an if...else statement, but it looks messy.

Conditional wrapping in React

Let's say we got specific service cards to make it a bit easier to follow. In some cases, they explain a service, but in others, they need to link to a detail page.

The component might look like this.

const ServiceCard = ({ title, description, image, url }) => {
  return (
    <section>
      <h2>{title}</h2>
      <p>{description}</p>
      <img src={image} alt={title} />
    </section>
  );
};
Enter fullscreen mode Exit fullscreen mode

As mentioned, what happens if we need to wrap this whole thing in a link element when the URL exists?
We could use the if...else loop.

const ServiceCard = ({ title, description, image, url }) => {
  return (
    <section>
      {url ? (
        <a href={url}>
          <h2>{title}</h2>
          <p>{description}</p>
          <img src={image} alt={title} />
        </a>
      ) : (
        <>
          <h2>{title}</h2>
          <p>{description}</p>
          <img src={image} alt={title} />
        </>
      )}
    </section>
  );
};
Enter fullscreen mode Exit fullscreen mode

But this shows duplicate code, so it's a bit silly. If we need to style each element, we must modify it in two places.

So how can we better wrap this conditionally?

We can create a generic component that handles this for us, the component will be named ConditionalWrapper, and it will take a condition, the wrapper, and the children it should wrap.

const ConditionalWrapper = ({ condition, wrapper, children }) =>
  condition ? wrapper(children) : children;
Enter fullscreen mode Exit fullscreen mode

With that in place, we can use it on our existing component to clean it up.

const ServiceCard = ({title, description, image, url}) => {
    return (
        <section>
            <ConditionalWrapper
                condition={url}
                wrapper={children => <a href={url}>{children}</a>}
            >
                <>
                    <h2>{title}</h2>
                    <p>{description}</p>
                    <img src={image} alt={title} />
                </>
            </ConditionalWrapper>
        </section>
    )
}
Enter fullscreen mode Exit fullscreen mode

And now, if we use our component, depending on whether we pass the URL. It will render with or without the href. And the best part is that we have no duplication in our elements.

For example, the following use case:

<ServiceCard title='test' description='foo bar' img='img1.jpg' />
Enter fullscreen mode Exit fullscreen mode

It would return the following HTML output:

<section>
  <h2>test</h2>
  <p>foo bar</p>
  <img src="img1.jpg" alt="test" />
</section>
Enter fullscreen mode Exit fullscreen mode

We will get the following output if we put the URL in the element.

<section>
  <a href="url">
    <h2>test</h2>
    <p>foo bar</p>
    <img src="img1.jpg" alt="test" />
  </a>
</section>
Enter fullscreen mode Exit fullscreen mode

Pretty cool, right?

The main magic, of course, happens in the ConditionalWrapper component and, to be precise, the wrapper argument.

Since we pass the children (which is a React default prop), we can see that the use case of our function as in wrapper={children => <a href={url}>{children}</a>} states.

  • If the condition is met
  • Wrap the children in this specific element

There will only be a handful of times when you might need this function, but it can be a huge lifesaver.

Note: big thanks to Olivier for the original idea!

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Oldest comments (43)

Collapse
 
yuridevat profile image
Julia ๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป GDE

Thanks for this really good piece of code ๐Ÿ™

Question: When you use Conditionalwrapper inside Servicecard, is the closing </a> right or should it be </>?

Collapse
 
musman0741 profile image
Muhammad Usman

Hi @julia
I believe that might be a typo from the author end, </> should be used.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Ah sorry my bad, should have been the fragment closing ๐Ÿ‘€

Collapse
 
lexlohr profile image
Alex Lohr

Great idea, Chris & Olivier!

This becomes even more powerful if you make it an array of (wrapper | false)[], so you can express multiple conditional wrappers in a more readable (less deep) structure:

const ApplyWrappers = ({ wrappers, children }) => {
  const wrapper = wrappers.shift()
  if (wrapper) {
    children = wrapper(children);
  }
  return wrappers.length
    ? <ApplyWrappers wrappers={wrappers} children={children} />
    : children;
}
Enter fullscreen mode Exit fullscreen mode

This allows you to use

<ApplyWrappers wrappers={[
  condition1 && (children) => <Whatever>{children}</Whatever>,
  condition2 && ...
]}>
  ...
</ApplyWrappers>
Enter fullscreen mode Exit fullscreen mode

P.S.: Wrote that on my cellphone, so this is untested.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Oh like that idea! ๐Ÿ‘€

Collapse
 
lexlohr profile image
Alex Lohr

I just realized this could be made even simpler using Array.prototype.reduce:

const ApplyWrappers = ({ wrappers, children }) => wrappers.reduce(
  (children, wrapper) => wrapper ? wrapper(children) : children,
  children
);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jareechang profile image
Jerry

Sir you are a legend.

Was this you ? ๐Ÿ‘‡

Image description

Collapse
 
lexlohr profile image
Alex Lohr

Nope, I used a normal cellphone. I don't think it would work, had I written it on that.

Collapse
 
venelinn profile image
Venelin

Great trick.
The only problem is that the else condition outputs invalid HTML. You shouldn't put block elements inside inline.
Here is a trick how to achieve similar functionality !
codepen.io/venelinn/pen/qBKBoPo

Collapse
 
dailydevtips1 profile image
Chris Bongers

Ah yeah didn't really pay much attention to what I wrapped for the example purpose.

Collapse
 
morokhovskyi profile image
Morokhovskyi Roman

Good material, Chris!

Collapse
 
darkterminal profile image
Imam Ali Mustofa

You jus't doing something unconventional method, and that metaphor really great! You're Code Freestyler!

Collapse
 
hectorzr profile image
Hector Zurga

This is the cleanest solution to conditional rendering I've seen so far!! Really clean implementation

Collapse
 
irepo profile image
Iraitz Puente

That sounds promising! I will test this in some of my projects! Thanks!

Collapse
 
hmphu profile image
Phu Hoang

Good thinking! Thanks for sharing

Collapse
 
sloloris profile image
Slo Loris

Clean code ๐Ÿคฉ

But cant we use React.CreateElement() and conditionally select element type? The last argument of the function takes children. Enlighten me if am wrong ๐Ÿค”

Collapse
 
dailydevtips1 profile image
Chris Bongers

Not sure I follow you mean for TypeScript safety?
Could you sketch the example here?

Collapse
 
sloloris profile image
Slo Loris • Edited
import React, { Attributes, useState } from 'react';

type Props = {
  title: string;
  description: string;
  image: string;
  url?: string;
};

const ServiceCard = ({ title, description, image, url }: Props) => {
  return (
    <section>
      {React.createElement(
        url ? 'a' : React.Fragment,
        ({ href: url } as Attributes) ?? null,
        <>
          <h2>{title}</h2>
          <p>{description}</p>
          <img src={image} alt={title} />
        </>
      )}
    </section>
  );
};

export default function App() {
  let url = undefined;
  return (
    <div>
      <ServiceCard title="foo" description="bar" image="foo.img" />
      <ServiceCard
        title="foo"
        description="bar"
        image="foo.img"
        url="https://www.google.com"
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Whole code with render. Ignore the types in the example as I have used TS. Works in JS too. Working example: stackblitz.com/edit/vitejs-vite-wb...

Thread Thread
 
dailydevtips1 profile image
Chris Bongers

Ah yes would work, but less reusable I guess when needing it more than once.

Collapse
 
danisalahi profile image
Dan Salahi
import React from 'react'


interface ConditionalWrapperProps {
    condition: any
    wrapper: (children: React.ReactNode) => React.ReactNode
    children: React.ReactNode
}

export const ConditionalWrapper: React.FC<ConditionalWrapperProps> = ({ condition, wrapper, children }) => {
    return <React.Fragment>
        condition ? wrapper(children) : children
    </React.Fragment>
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
petosim profile image
Simon Petovar

Nice idea! ๐Ÿ™Œ