React: Conditional Rendering
We often need to add or exclude elements based on some condition. This is called conditional rendering. Let's dig into some ways that conditional rendering is done in React, as well as any gotcha that they may come with.
if
Statements
With {}
curly brackets, we can embed JavaScript expressions into our JSX, but we can't embed JavaScript statements. As a result, if
statements don't typically work:
function Profile({ name, isActive }) { return ( <li className="profile"> {if (isActive) { // expected result: SyntaxError <div className="green-dot" /> }} {name} </li> );}
export default Profile;
That's because this transpiles to the following:
import React from "react";
function Profile({ name, isActive }) { return React.createElement( "li", { className: "profile", }, if (isActive) { // expected result: SyntaxError React.createElement( "div", { className: "green-dot", } ) }, name )}
export default Profile;
Instead, if we pull the if
statement out of the return
statement, we can use it:
function Profile({ name, isActive }) { let statusIndicator; if (isActive) { statusIndicator = <div className="green-dot" />; }
return ( <li className="profile"> {statusIndicator} {name} </li> );}
export default Profile;
This transpiles to:
import React from "react";
function Profile({ name, isActive }) { let statusIndicator;
if (isActive) { statusIndicator = React.createElement("div", { className: "green-dot", }); }
return React.createElement( "li", { className: "profile", }, statusIndicator, name );}
export default Profile;
In this example, statusIndicator
will either be assigned to a React element, or it won't be defined at all. This works because React ignores children that don't produce a value. If isActive
is (or evaluates to) false
then React doesn't create that element.
Logical And Operator &&
The downside with if
statements is that we need to remove the logic from the markup. The &&
operator lets us keep the condition inline with the markup:
function Profile({ name, isActive }) { return ( <li className="profile"> {isActive && <div className="green-dot" />} {name} </li> );}
export default Profile;
In JavaScript, &&
is a control flow operator. It works like an if
statement, but crucially here it is an expression, not a statement.
Here is the same code, but written as an if/else
statement:
function Profile({ name, isActive }) { let statusIndicator; if (isActive) { statusIndicator = <div className="green-dot" />; } else { statusIndicator = isActive; }
return ( <li className="profile"> {statusIndicator} {name} </li> );}
export default Profile;
Like an if/else
, the &&
will always return one of two provided paths. If the first value provided (on the left hand side of the operator) is *falsy**, it will short-circuit and not return anything at all.
* technically this isn't true, it will return that first, falsy value. But when it evaluates in the expression, that falsy value will not render anything at all. The following don't result in anything at all:
{ null && <p>Never gets here</p>;} // nothing happens{ undefined && <p>Never gets here</p>;} // nothing happens{ "" && <p>Never gets here</p>;} // nothing happens
However, falsy values that are typeof(Number)
will get returned from the left hand side, and React will render those:
{ 0 && <p>Never gets here</p>;} // 0 renders{ +0 && <p>Never gets here</p>;} // 0 renders{ -0 && <p>Never gets here</p>;} // 0 renders{ NaN && <p>Never gets here</p>;} // NaN renders
All other values are considered truthy, so the &&
operator returns the right hand side operand:
{ true && <p>Howdy world</p>;} // <p>Howdy world</p> renders{ "hello" && <p>Howdy world</p>;} // <p>Howdy world</p> renders
That said, if the right hand operand is falsy then that's what gets returned, so the same falsy gotchas apply:
{ true && null;} // nothing happens{ true && undefined;} // nothing happens{ true && "";} // nothing happens{ true && 0;} // 0 renders{ true && +0;} // 0 renders{ true && -0;} // 0 renders{ true && NaN;} // NaN renders
However, in most React contexts, the right hand operand will be a React component. So as a way to avoid the weirdness with 0
, +0
, -0
, and NaN
rendering from the &&
operator, the left hand operand should always be or evaluate to a boolean.
If we're checking a number, we can do something like the following:
function App() { const shoppingList = ["avocado", "banana", "cinnamon"]; const numOfItems = shoppingList.length;
return <div>{numOfItems > 0 && <ShoppingList items={shoppingList} />}</div>;}
Or do some coercion:
function App() { const shoppingList = ["avocado", "banana", "cinnamon"]; const numOfItems = shoppingList.length;
return <div>{!!numOfItems && <ShoppingList items={shoppingList} />}</div>;}
A related approach here is the Null Coalescing Operator (??
). This operator will return the right hand operand if the left hand operand is Nullish (i.e. either undefined
or null
).
{ null ?? <p>This renders</p>;}{ undefined ?? <p>This renders</p>;}{ "" ?? <p>Never gets here</p>;}
Ternary Operator ?:
While the &&
operator is great for inlining if
statement flow control into our markup, ternary operators are great for inlining if
/else
into markup.
function Profile({ name, isActive }) { return ( <li className="profile"> {isActive ? <div className="green-dot" /> : <div className="red-dot" />} {name} </li> );}
export default Profile;
Here is the same code, but written as an if/else
statement:
function Profile({ name, isActive }) { let statusIndicator; if (isActive) { statusIndicator = <div className="green-dot" />; } else { statusIndicator = <div className="red-dot" />; }
return ( <li className="profile"> {statusIndicator} {name} </li> );}
export default Profile;
Hiding with CSS
Another route would be to hide with CSS:
function Profile({ name, isActive }) { const style = isActive ? undefined : { display: "none" };
return ( <li className="profile"> <div className="green-dot" style={style} /> {name} </li> );}
export default Profile;
In this casde, if the profile is online and isActive === true
, then style
will simply be undefined
and have no effect, the green dot will be shown. Otherwise display: none;
will apply, hiding the green dot. Neat!
Comparing Approaches
The JS logic vs. CSS logic effectively achieves the same thing, but there are some performance considerations. Sometimes it's less memory intensive to not have hidden nodes exist at all. In that case, JS logic that doesn't put the DOM elements onto the page would be better.
On the other hand, adding a brand-new DOM node to the page is a much slower task than toggling a CSS property, so if the user is toggling the content on and off (like a flyout menu), it might be faster to use the display
property.
But at the end of the day, you should test, evaluate, and make adjustments to test other approaches if necessary!
Review
if
statements only work if we remove the logic from the rendering portion of the React component- The Logical And Operator
&&
is probably the most common form of conditional rendering in React. If the first value provided (the left hand operand) is falsy, it will short-circuit and not return anything at all, otherwise it will return the second value provided (the right hand operand)- If the left hand operand is a falsy value that is
typeof(Number)
, then0
orNaN
will render. This is caused by a decision the React team made when designing the library
- If the left hand operand is a falsy value that is
- The Ternary Operator
?:
produces one of two provided outcomes based on the test condition (first operand of the operator). If the test condition istrue
or evaluates totrue
, then the second expression (after the?
) will be returned. If the test condition isfalse
or evaluates tofalse
, then the third expression (after the:
) will be returned - You can use any of these techniques to return JSX elements or React components based on some logic, or use these approaches in conjunction with CSS to hide elements using
display: none;