DEV Community

Cover image for Conditional wrapping in React
Chris Bongers
Chris Bongers

Posted on • Updated 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

Latest comments (43)

Collapse
 
potouridisio profile image
Ioannis Potouridis

What would happen if your ServiceCard component had a state?

wrapper={children => <a href={url}>{children}</a> would cause the children to re-render, which in your example is acceptable but people will use ConditionalWrapper in different cases. Imagine wrapping animated elements, form elements, charts, maps etc.

What happens to the code if I want to use a different wrapper component in one ServiceCard?

My answer to conditional wrapping is always polymorphic components.

This would be your new component.

function ServiceCard(props) {
  const { description, img, title, wrapperComponent: WrapperComponent = 'div', ...wrapperComponentProps } = props;

  return (
    <section>
      <WrapperComponent {...wrapperComponentProps}>
        <h2>{title}</h2>
        <p>{description}</p>
        <img alt="test" src={img} />
      </WrapperComponent>
    </section>
  )
}
Enter fullscreen mode Exit fullscreen mode

Then use it as is to use a div.

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

Or an anchor element

<ServiceCard
  description="foo bar"
  href="get-me-out-of-here"
  img="img1.jpg"
  title="test"
  wrapperComponent="a" 
/>
Enter fullscreen mode Exit fullscreen mode

Or even a router link

<ServiceCard
  description="foo bar"
  img="img1.jpg"
  title="test"
  to="get-me-out-of-here"
  wrapperComponent={Link} 
/>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
lucaswinkler profile image
Lucas Winkler

This helps so much for my current project. Still got a lot of learning to do and I was trying to find a way around code duplication for a my main navigation.

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
 
valiant600 profile image
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
 
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
 
uzomanwanne profile image
Uzoma Nwanne

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

Collapse
 
sohrab09 profile image
Mohammad Sohrab Hossain

Great article.

Thanks man

Collapse
 
yukikimoto profile image
Yuki Kimoto - SPVM Author

I feel this is similar with Mojo::Template.

<%= $flag ? 'Foo' : 'Bar' %>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ismailcherri profile image
Ismail Cherri

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

Collapse
 
dailydevtips1 profile image
Chris Bongers

Awesome! I'll go check it out

Collapse
 
sloloris profile image
Slo Loris

Thats something handy 😃

Collapse
 
joanroucoux profile image
Joan Roucoux

Thanks for sharing!

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
 
petosim profile image
Simon Petovar

Nice idea! 🙌

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
 
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
 
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
 
hmphu profile image
Phu Hoang

Good thinking! Thanks for sharing