OpenReplay
Navigate back to the homepage
BLOG
Browse Repo
Back

7 Ways of Achieving Conditional Rendering in React

Fernando Doglio
December 28th, 2020 · 9 min read

Choosing when to show a piece of your UI should be as easy as writing an IF statement:

1if(condition_is_met) {
2 renderSectionOfUI();
3 }

However, given the component-based nature of React, and the mixture of HTML and JavaScript known as JSX, that IF gets a little bit more complicated depending on where exactly we want to add it. Should we add it as part of our component’s code? Can we add an IF right in our HTML? Are there other options?

In this article, I’m going to be covering the 7 most common ways of achieving what is known as “conditional rendering” in React. This should give you an idea of what is possible to do and why.

Implementing conditional rendering

Although choosing to conditionally render a component should be easy, to make it easy we need to understand the behavior of the framework we’re using. Simply put, we can’t just add an IF right inside our JSX code, because JSX is nothing more than syntactic sugar, designed for our benefit but lacking any magic that we might want to attribute to it.

Put simply, the following JSX code:

1ReactDOM.render(<div id="error-box">This is my error message!</div>, mountNode);

Is translated into:

1ReactDOM.render(React.createElement("div", {id:"error-box"}, "This is my error message!"), mountNode);

And adding an IF inside our code like this:

1ReactDOM.render(<div id={if (condition) { 'error-box' }}>This is my error message!</div>, mountNode)

Would translate to the following, invalid JS (you can’t add an IF as the value of a JSON key):

1ReactDOM.render(React.createElement("div", {id: if (condition) { 'error-box' }}, "This is my error message!"), mountNode);

But don’t worry, there are more options available to achieve exactly this (and even more complex behavior) than you might ever need, and we’ll take a look at them right now.

Using IF/Else inside your components

This is the most straightforward and easy to understand, since it directly covers JS and how JS works. You don’t need to worry about JSX here or anything. This technique requires you to extract the IF statement I showed before and add it before the call the render. All you have to remember is to set the correct value for the id attribute inside a variable and then you can use it in JSX normally:

1render() {
2 //your component's render method....
3 let idAttr = ''
4 if(condition) {
5 idAttr = 'error-box'
6 }
7 //... more logic here...
8 return (<div id={idAttr}>This is my error message!</div>)
9 }

You can do the same with a functional component as well as the following variation:

1const myMsgBox = () => {
2 let idAttr = ''
3 if(condition) {
4 return (<div id="error-box">This is my error message!</div>)
5 }
6 return (<div id="msg">Normal message, no error here!</div>)
7 }

Either case, they would work and you would have your conditional rendering logic ready.

JSX syntax: taking advantage of JavaScript’s &&

But of course, there are other ways of doing the same thing, because this is programming after all. As I already mentioned, you can’t add an IF statement inside your JSX, because it doesn’t translate well into proper JS. However, you can use a boolean expression instead. Have you ever seen code like this?

1function fnName(optionalAttr) {
2 let myVar = optionalAttr && "default value";
3 //...more logic here
4 }

In JS the above code would assign the string "``default value``" to the variable myVar if optionalAttr wasn’t present. Of course, it would also assign the default value if optionalAttr was an empty string or the numeric value 0. This is because we’re using a boolean expression that, by default in JS, always evaluates until it finds a falsy value.

For us, this means we can add our condition for render before the call to the component that needs to be rendered. This in turn, would cause our component to only be shown if the condition is true. Something like this:

1function MyComp(props) {
2 const errorCondition = props.errorCondition;
3 return (
4 <div>
5 <h1>Hello!</h1>
6 {errorCondition &&
7 <errorMessage>This is an error</errorMessage>
8 }
9 </div>
10 );
11 }

We’re conditionally rendering our errorMessage component only if the errorCondition variable has a truthy value, otherwise JS will stop evaluating our code in that variable and it would never reach the errorMessage portion of our logic.

The ternary operator

Given the fact that we can do that with the && operator, we can also do something very similar with the ternary operator. Otherwise known as inline IF, which allows us not only to conditionally render a component (or part of it) but also to add an “else” behavior. Check it out:

1function MyComp(props) {
2 const errorCondition = props.errorCondition;
3 return (
4 <div>
5 <h1>Hello!</h1>
6 {errorCondition
7 ? <ErrorMessage>This is an error</ErrorMessage>
8 : <InfoMessage>Hey there bud! Nothing wrong here, carry on!</InfoMessage>
9 }
10 </div>
11 );
12 }

Notice how we’re even able to structure it in a way that it looks like an IF statement somehow. I mean, IT IS an IF statement, in essence, but written as an expression, which is valid for JS. This will either, render the ErrorMessage component if errorCondition evaluates to TRUE or otherwise it will render InfoMessage. Note that this is a value expression, because just like in the previous example, you could potentially assign the result to a variable (since it returns the result of evaluating either one of the sub-expressions inside it):

1function fnName(optionalAttr) {
2 let myVar = (optionalAttr != null) ? "default value" : optionalAttr;
3 //...more logic here
4 }

The above code will assign "``default value``" to myVar only when optionalAttr is null, otherwise it will always assign its value.

Using null to avoid rendering

This is yet another way of choosing to avoid rendering a component, but this time it has nothing to do with how JS uses conditions and more to do with how React behaves. As you may or may not know, React will not render a thing in place of your component, if its render method (or itself if it’s a functional component) returns null. So if we want to move the conditional rendering logic to inside our affected component (as opposed to having it outside, as part of another component), we can do so by making it return null when it needs to hide.

Given the following consuming component:

1class Page extends React.Component {
2 constructor(props) {
3 super(props);
4 this.state = {showError: true};
5 }
6 //more methods here...
7 render() {
8 return (
9 <div>
10 <ErrorMessage show={this.state.showError} />
11 </div>
12 );
13 }
14 }

We can write our ErrorMessage component like this:

1function ErrorMessage(props) {
2 if (!props.show) { return null; }
3 return (
4 <div className="error">
5 Error!
6 </div>
7 );
8 }

I like this solution better because it keeps JSX code (i.e presentation code) clean, while hiding the logic behind whether or not a component should be rendered inside pure JS and I’m all about separation of concerns. This way we keep our view code focused on presentation and structure, while our JS code takes care of any logic we need.

Understanding IF components

Following on that same note, we can go even one step further, and hide the entire IF statement logic inside a component. This in turn would create a hybrid of a component, one that instead of representing a visual component, would represent a logic component, but still keeping the view code clean by only writing HTML-like tags. I’m of course, referring to the IF component. In JS we write our IF statements following always the same pattern:

1if(condition) {
2 truthyBehavior()
3 }

And our IF component is no different, we can do something like this by taking advantage of child components:

1<IF condition={condition}>
2 <TruthyBehavior>
3 </IF>

And as long as condition is a value boolean expression, we can write our IF component like this:

1function IF(props) {
2 if(props.condition) {
3 return props.children
4 }
5 return null;
6 }

Notice how we’re also using the null default behavior here. By creating this generic component, we can use it across our projects, adding logic to our view code with minimum impact. The problem with this implementation, is that the child components will be evaluated regardless of the condition and only shown if we need to. This can potentially be a problem if you link the children with the condition. For example:

1<IF condition={user != null}>
2 <Greeter username={user.name} />
3 </IF>

In this case, we would see an error message from the browser, stating that user is null, even though we intended that code to only be executed if user was defined. This is a problem because even though our code might resemble a normal IF statement, it is not and we’re tricked to mentally follow the expected behavior. This means we’re either forced to keep this behavior in mind or change our component into something that uses functions to encapsulate logic we want:

1function IF(props) {
2 if(props.condition) {
3 return props.onTrue()
4 }
5 return null;
6 }

And then we can use it like this:

1<IF condition={user != null} onTrue={() => {
2 <Greeter username={user.name} />
3 }
4 }/>

The resulting code is not as clean, but it gets the job done. Now since we’re only executing our code when the anonymous function gets called, we’re safe to use the user.name property. Writing a proper and safe IF component is not easy, so keep reading to find out other ways of conditionally rendering a component.

Frontend Monitoring

Debugging a web application in production may be challenging and time consuming. OpenReplay is an open-source session replay stack for developers. It helps you replay everything your users do and shows how your app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder.

OpenReplay lets you reproduce issues, aggregate JS errors and monitor your app’s performance. OpenReplay offers plugins for capturing the state of your Redux or VueX store and for inspecting Fetch requests and GraphQL queries.

text

Happy debugging, for modern frontend teams - Start monitoring your web app for free.

Using HOCs to achieve conditional rendering

HOCs or Higher-Order Components are functions that accept components as parameters and return new ones as a result of their execution. They help create wrapper components if you will, that can help us add logic (more specifically, rendering logic) to a plain component. Or that can also help us, extract logic into a generic component, letting us clean up our main component’s code.

For example, let’s take another look at our running example component:

1function ErrorMessage(props) {
2 if (!props.show) { return null; }
3 return (
4 <div className="error">
5 Error!
6 </div>
7 );
8 }

We could create a HOC that removes the first IF statement from the component, like this:\

1function showOnlyWhenNeeded(conditionalFn, component) {
2 return function(props) {
3 if(conditionalFn(props)) {
4 return component(...props)
5 }
6 return null
7 }
8 }

We can then, proceed to use this HOC like this:

1//...your code
2 function ErrorMessage(props) {
3 //no more IF statement here
4 return (
5 <div className="error">
6 Error!
7 </div>
8 );
9 }
10
11 let betterErrorMessage = showOnlyWhenNeeded(props => props.show, ErrorMessage)
12
13 //... more code here
14 <betterErrorMessage show={condition} />

This approach has two immediate benefits:

  1. The code for your component gets cleaned up, because it no longer requires adding the conditional rendering logic inside it. That part is abstracted into an external function,
  2. The conditional rendering logic can be extended simply by adding more function composition to the mix.

Look at the following example:

1let betterErrorMessage = showOnlyWhenNeeded(props => props.show, ErrorMessage)
2 let myBetterErrorMessage = showOnlyWhenNeeded(props => props.username == "fernando", betterErrorMessage)
3 let myBetterErrorMessageForHomepage = showOnlyWhenNeeded(props => props.currentpage == 'home', myBetterErrorMessage)
4 //... more code here
5 <myBetterErrorMessageForHomepage show={condition} username={currentUser} currentPage={page} />

Granted, the example might be a little basic, but you get the point. I added three different rendering conditions to my component without having to modify its code. That’s a win in my book.

Using fragments to achieve conditional rendering

Fragments allow you to return a set of elements or components without a wrapping HTML tag. For example, a classic example would be to create a component that needs to render several table cells inside a row, like this:

1function tableRow() {
2 ///your logic here
3 return (<tr>
4 {tableCells()}
5 </tr>)`
6 }

A normal tableCells component would need to return several td elements. However, we would have to wrap them inside another tag, like a div, which in turn would break everything. For this scenario, React added the concept of Fragments. By using them, we can return a set of elements inside a generic, non-descript tag only used by React itself, like this:

1function tableCells() {
2 return (<>
3 <td>Val1</td><td>Val2</td>
4 </>)
5 }

And we can use this concept to also add conditional rendering to fragments. In fact, we can just use any of the methods listed so far, they all work with fragments. The following component shows how to return a list of fragments, out of which, only some of them get rendered:

1function Lines() {
2 let children = ['Some text.',
3 <h2>A heading</h2>,
4 'More text.',
5 <h2>Another heading</h2>,
6 'Even more text.']
7 return (
8 <>
9 {children.map( (child, idx) => {
10 if(idx % 2 == 0) return child;
11 return null;
12 })
13 }</>
14 );
15 }

As you can appreciate, the rendering logic is added inside the component on the return statement, by returning null for the children that won’t need to be rendered.

Conditional rendering with Hooks

Functional components and hooks simplified the way we think about components and the way we write them. However, when you’re working with conditionally rendered components, sometimes this new way of writing them might get in the way.

One thing that not everyone takes into account, is that React relies on the fact that functional components need to always call the same hooks in the same order. This is crucial for everything to work. If you have a component looking like this:

1function myComp(props) {
2 let state, setState = useState({name: 'Fernando'})
3
4 if(state.name == 'Mary') {
5 useEffect(function persistForm() {
6 localStorage.setItem('formData', state.name);
7 });
8 }
9
10 //more code here
11 }

The useEffect hook will not always be called, and this is not gonna work with React. T is why if we attempt to use some of the techniques shown so far inside our functional components and affect the way hooks are called, our conditionally rendered component is going to make our app go “boom”. In other words, this will not work:

1function myComp(props) {
2 if(!props.show) return null;
3
4 let state, setState = useState({name: 'Fernando'})
5
6 useEffect(function persistForm() {
7 localStorage.setItem('formData', state.name);
8 });
9
10 //more code here
11 }

Just add that first line at the bottom, and deal with any if -based condition for your other hooks inside them. Like the useEffect hook, if you need to only have that logic working when the component is being rendered, add that if inside its callback:

1function myComp(props) {
2 let state, setState = useState({name: 'Fernando'})
3
4 useEffect(function persistForm() {
5 if(props.show){
6 localStorage.setItem('formData', state.name);
7 }
8 });
9 if(!props.show) return null;
10 //more code here
11 }

Either that, or just rely on any of the other techniques that don’t affect the order of execution of the hooks.

Performance considerations

Conditional rendering in React is great and allows for very dynamic behavior, however, it does not come free. Depending on how you decide to implement it, the performance costs can be considerable. Of course, there are many optimizations you can implement regarding your own code and logic associated with your rendering logic, however, there is one big topic to understand about React before you decide to call it a day: React cares about the order in which you render your components.

Essentially, if your rendering logic will keep moving components out of order, then all associated components will have to be unmounted and remounted again, even the ones you’re not trying to hide or show. Let me explain:

The most common example, happens if you follow the first technique described here (mind you, this will only become a problem if you’re overusing it, otherwise the performance penalty is barely visible). With our first example, we were doing something like this:

1const myMsgBox = () => {
2 let idAttr = ''
3 if(condition) {
4 return (<div id="error-box">This is my error message!</div>)
5 }
6 return (<div id="msg">Normal message, no error here!</div>)
7 }

Visually, we’re getting our results, because we’re either rendering an error box or an info box. But internally, React is having to unmount both components every time the rendering condition changes. And this problem is even more apparent when we do something like this:

1const myMsgBox = () => {
2 let idAttr = ''
3 if(condition) {
4 return (<div>
5 <ErrorBox id="error-box">This is my error message!</ErrorBox>
6 <InfoBox>This text is always visible</InfoBox>
7 </div>)
8 }
9 return (<div>
10 <InfoBox>This text is always visible</InfoBox>
11 </div>)
12 }

We’re not using 2 different components inside our myMsgBox one. We’re telling React that if there is an error, we need to show the ErrorBox as well as the InfoBox with a permanent message. However, as logic would dictate, if there is nothing wrong, we don’t need to render the ErrorBox. The problem here? That on our first render, with let’s say, no error condition, React rendered the InfoBox on position #1, but on the second render, with an error condition, the component rendered in position #1 will be the ErrorBox while also having the InfoBox rendered on position #2.

Abusing this behavior will cause React to keep mounting and unmounting our components, even though some of them need to be present all the time (i.e the InfoBox). The way to solve this, is to go for a technique, such as the null-based one. Because if a component is replaced by null, the “slot” it occupies will not be replaced by the next component in line, instead, it will be help there, empty sort of speak, by the null value.

For example, something like this:

1const MyMsgBox = () => {
2 let [condition, setCondition] = React.useState(false)
3 return (<div>
4 {condition &&
5 <ErrorBox id="error-box">This is my error message!</ErrorBox>}
6 <InfoBox>This text is always visible</InfoBox>
7 <button onClick={() => setCondition(!condition)}>Toggle</button>
8 </div>)
9 }

Every time we click the button, it will change the component’s state, and the condition will be updated. Because we’re using this circuit-break syntax, when the condition value is false, it will leave a false value instead of the ErrorBox component (which means the same as a null value), and when it’s true, it will render mount and render the ErrorBox correctly. The InfoBox component is never touched nor affected.

Closing thoughts

Conditional rendering with React is a relatively easy thing to do, the hard part is doing it in a way that solves your problem correctly.

There are performance considerations to account for, of course, such as the ones mentioned above, but you also need to consider the effect your logic will have on your code. The moment you start adding behavior to your presentation, you have to remember to keep best practices present as well. Keep your view code clean, or at least as clean as possible. Remember, JSX is not HTML nor JavaScript, it’s a mixture, but it doesn’t mean you have to forget about separation of concerns!

More articles from OpenReplay Blog

JavaScript Event Loop And Call Stack Explained

JavaScript Event Loop is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.

December 25th, 2020 · 9 min read

React’s onClick Event Handler Explained

The React onClick event handler enables you to call a function and trigger an action when a user clicks an element, such as a button.

December 23rd, 2020 · 3 min read
© 2021 OpenReplay Blog
Link to $https://twitter.com/OpenReplayHQLink to $https://github.com/openreplay/openreplayLink to $https://www.linkedin.com/company/18257552