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

Top comments (43)

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
 
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
 
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
 
devtalhaakbar profile image
Muhammad Talha Akbar

Definitely thought-provoking. An abstraction like ConditionalWrapper component is one way of doing it in this simplified use case. But it may need serious consideration in the real setting. How about refactoring the duplicated part into a separate component e.g. ServiceCardDetails while keeping the JSX expression?

const ServiceCardDetails = ({ title, description, image }) => (
  <>
   <h2>{title}</h2>
    <p>{description}</p>
    <img src={image} alt={title} />
  </>
)

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

Abstractions are nice but duplication is far superior if the abstraction does not withstand real challenges. Only you as an engineer can tell if such abstraction will continue to deliver value in the long-run.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Yeah kind of depends on the use-case sometimes your scenario might be the better abstraction.
Some times it might be the ConditionalWrapper.

It comes down to how the abstraction takes place.

Collapse
 
vhoyer profile image
VinΓ­cius Hoyer • Edited

Aren't we overthinking this, doesn't this works fine? (Not react dev here)

export default comp({ isCond }) {
  const Wrapper = isCond? 'a' : React.Fragment;

  return (<Wrapper>content</Wrapper>);
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dailydevtips1 profile image
Chris Bongers

Not sure how my example is complicated to be honest?
Seems pretty straightforward.

Collapse
 
vhoyer profile image
VinΓ­cius Hoyer

My point was that it seemed like an unnecessary abstraction since the "wet" code is not really a bother to use, you understand?

Not that your code was complicated by any means

Collapse
 
gitaakashstack profile image
gitaakashstack

Yes Even I am thinking the same....what's the need to complicate....even this keeps our code DRY

Collapse
 
dailydevtips1 profile image
Chris Bongers

The only code that really matter is this piece:

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

So seems pretty simple right?
The other is just examples of the use-case really.

Hope that clarifies some things πŸ™

Collapse
 
gitaakashstack profile image
gitaakashstack • Edited

Why it couldn't be as simple as this below ?

let Wrapper = React.Fragment //fallback in case you dont want to wrap your components

if(someCondition) {
    Wrapper = ParentComponent
}

return (
    <Wrapper parentProps={parentProps}>
        <Child></Child>
    </Wrapper>
)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
dailydevtips1 profile image
Chris Bongers

This is kind of the extended write down of the function I described.
So if that's easier for you that will work.

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
 
dailydevtips1 profile image
Chris Bongers

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

Collapse
 
musman0741 profile image
Muhammad Usman

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

Collapse
 
ayka_code profile image
ayka.code

Hi Chris,

I really enjoyed your article on using ConditionalWrapper for conditional wrapping in React. In my recent blog post, I introduce an advanced approach using higher order components (HOCs). I found this method to be more flexible and customizable, and I think it might be useful for others looking to add additional functionality to their wrapped elements. Thank you for sharing your technique, and I hope my approach might be of interest to your readers as well.

Collapse
 
dailydevtips1 profile image
Chris Bongers

Thanks for that, help me understand though what's the difference between my generic component and your HOC?
It's just terminology, right?

Collapse
 
ayka_code profile image
ayka.code

In a sense, the terms "generic component" and "HOC" are just labels that we use to describe different types of components in software development. However, there are some practical differences between these two types of components that go beyond just the terminology.

An HOC is a function that takes a component and returns a new, enhanced component. This allows you to reuse code and abstract common logic across multiple components, and can be a powerful tool for creating reusable abstractions in your code.

On the other hand, a generic component is simply any component that is not an HOC. It could be a presentational component that only handles the rendering of UI elements, or a container component that handles the data management and behavior of your application.

So while the terms "generic component" and "HOC" are just labels, they do reflect some practical differences in how these components are used and what their purpose is.

Collapse
 
ismailcherri profile image
Ismail Cherri

I created a library that solves these kind of problems: npmjs.com/package/react-jsx-flow

Collapse
 
sloloris profile image
Slo Loris

Thats something handy πŸ˜ƒ

Collapse
 
dailydevtips1 profile image
Chris Bongers

Awesome! I'll go check it out

Collapse
 
yukikimoto profile image
Yuki Kimoto

I feel this is similar with Mojo::Template.

<%= $flag ? 'Foo' : 'Bar' %>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
valiant600 profile image
Collapse
 
uzomanwanne profile image
Uzoma Nwanne

Looks like a clean implementation. I will try it out later.