How do I conditionally wrap a React component?

JavascriptReactjs

Javascript Problem Overview


I have a component that will sometimes need to be rendered as an <anchor> and other times as a <div>. The prop I read to determine this, is this.props.url.

If it exists, I need to render the component wrapped in an <a href={this.props.url}>. Otherwise it just gets rendered as a <div/>.

Possible?

This is what I'm doing right now, but feel it could be simplified:

if (this.props.link) {
	return (
		<a href={this.props.link}>
			<i>
				{this.props.count}
			</i>
		</a>
	);
}

return (
	<i className={styles.Icon}>
		{this.props.count}
	</i>
);

UPDATE:

Here is the final lockup. Thanks for the tip, @Sulthan!

import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';

export default class CommentCount extends Component {

	static propTypes = {
		count: PropTypes.number.isRequired,
		link: PropTypes.string,
		className: PropTypes.string
	}

	render() {
		const styles = require('./CommentCount.css');
		const {link, className, count} = this.props;

		const iconClasses = classNames({
			[styles.Icon]: true,
			[className]: !link && className
		});

		const Icon = (
			<i className={iconClasses}>
				{count}
			</i>
		);

		if (link) {
			const baseClasses = classNames({
				[styles.Base]: true,
				[className]: className
			});

			return (
				<a href={link} className={baseClasses}>
					{Icon}
				</a>
			);
		}

		return Icon;
	}
}

Javascript Solutions


Solution 1 - Javascript

Just use a variable.

var component = (
    <i className={styles.Icon}>
       {this.props.count}
    </i>
);

if (this.props.link) {
    return (
        <a href={this.props.link} className={baseClasses}>
            {component}
        </a>
    );
}

return component;

or, you can use a helper function to render the contents. JSX is code like any other. If you want to reduce duplications, use functions and variables.

Solution 2 - Javascript

Create a HOC (higher-order component) for wrapping your element:

const WithLink = ({ link, className, children }) => (link ?
  <a href={link} className={className}>
    {children}
  </a>
  : children
);

return (
  <WithLink link={this.props.link} className={baseClasses}>
    <i className={styles.Icon}>
      {this.props.count}
    </i>
  </WithLink>
);

Solution 3 - Javascript

Here's an example of a helpful component I've seen used before (not sure who to accredit it to) which might be more declarative:

const ConditionalWrap = ({ condition, wrap, children }) => (
  condition ? wrap(children) : children
);

Use case:

// This children of this MaybeInAModal component will appear as-is or within a modal
// depending on whether "shouldOpenInModal" is truthy
const MaybeInAModal = ({ children, shouldOpenInModal }) => {
  return (
    <ConditionalWrap
      condition={shouldOpenInModal}
      wrap={wrappedChildren => (<Modal>{wrappedChildren}</Modal>)}
        {children}
    </ConditionalWrap>
  );
}

Solution 4 - Javascript

There's another way you could use a reference variable

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>

)

Solution 5 - Javascript

const ConditionalWrapper = ({ condition, wrapper, children }) => 
  condition ? wrapper(children) : children;

The component you wanna wrap as

<ConditionalWrapper
   condition={link}
   wrapper={children => <a href={link}>{children}</a>}>
   <h2>{brand}</h2>
</ConditionalWrapper>

Maybe this article can help you more https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2

Solution 6 - Javascript

You should use a JSX if-else as described here. Something like this should work.

App = React.creatClass({
	render() {
		var myComponent;
		if(typeof(this.props.url) != 'undefined') {
			myComponent = <myLink url=this.props.url>;
		}
		else {
			myComponent = <myDiv>;
		}
		return (
			<div>
				{myComponent}
			</div>
		)
	}
});

Solution 7 - Javascript

You could also use a util function like this:

const wrapIf = (conditions, content, wrapper) => conditions
        ? React.cloneElement(wrapper, {}, content)
        : content;

Solution 8 - Javascript

Using react and Typescript

let Wrapper = ({ children }: { children: ReactNode }) => <>{children} </>

if (this.props.link) {
    Wrapper = ({ children }: { children: ReactNode }) => <Link to={this.props.link}>{children} </Link>
}

return (
    <Wrapper>
        <i>
            {this.props.count}
        </i>
    </Wrapper>
)

Solution 9 - Javascript

A functional component which renders 2 components, one is wrapped and the other isn't.

Method 1:

// The interesting part:
const WrapIf = ({ condition, With, children, ...rest }) => 
  condition 
    ? <With {...rest}>{children}</With> 
    : children

 
    
const Wrapper = ({children, ...rest}) => <h1 {...rest}>{children}</h1>


// demo app: with & without a wrapper
const App = () => [
  <WrapIf condition={true} With={Wrapper} style={{color:"red"}}>
    foo
  </WrapIf>
  ,
  <WrapIf condition={false} With={Wrapper}>
    bar
  </WrapIf>
]

ReactDOM.render(<App/>, document.body)

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

This can also be used like this:

<WrapIf condition={true} With={"h1"}>

Method 2:

// The interesting part:
const Wrapper = ({ condition, children, ...props }) => condition 
  ? <h1 {...props}>{children}</h1>
  : <React.Fragment>{children}</React.Fragment>;   
    // stackoverflow prevents using <></>
  

// demo app: with & without a wrapper
const App = () => [
  <Wrapper condition={true} style={{color:"red"}}>
    foo
  </Wrapper>
  ,
  <Wrapper condition={false}>
    bar
  </Wrapper>
]

ReactDOM.render(<App/>, document.body)

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Solution 10 - Javascript

With provided solutions there is a problem with performance: https://medium.com/@cowi4030/optimizing-conditional-rendering-in-react-3fee6b197a20

React will unmount <Icon> component on the next render. Icon exist twice in different order in JSX and React will unmount it if you change props.link on next render. In this case <Icon> its not a heavy component and its acceptable but if you are looking for an other solutions:

https://codesandbox.io/s/82jo98o708?file=/src/index.js

https://thoughtspile.github.io/2018/12/02/react-keep-mounted/

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrandon DurhamView Question on Stackoverflow
Solution 1 - JavascriptSulthanView Answer on Stackoverflow
Solution 2 - JavascriptDo AsyncView Answer on Stackoverflow
Solution 3 - JavascriptantonyView Answer on Stackoverflow
Solution 4 - JavascriptAvinashView Answer on Stackoverflow
Solution 5 - JavascriptAdam AnjumView Answer on Stackoverflow
Solution 6 - Javascriptuser2027202827View Answer on Stackoverflow
Solution 7 - JavascriptTomek View Answer on Stackoverflow
Solution 8 - JavascriptMustkeem KView Answer on Stackoverflow
Solution 9 - JavascriptvsyncView Answer on Stackoverflow
Solution 10 - JavascriptsytolkView Answer on Stackoverflow