DEV Community

Cover image for Conditional wrapping in React

Conditional wrapping in React

Chris Bongers on December 11, 2022

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...
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
 
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
 
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
 
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 - SPVM Author

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.

Collapse
 
darkterminal profile image
Imam Ali Mustofa

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

Collapse
 
joanroucoux profile image
Joan Roucoux

Thanks for sharing!

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
 
morokhovskyi profile image
Morokhovskyi Roman

Good material, Chris!

Collapse
 
petosim profile image
Simon Petovar

Nice idea! 🙌

Collapse
 
sohrab09 profile image
Mohammad Sohrab Hossain

Great article.

Thanks man

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
 
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
 
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
 
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.