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), then 0 or NaN will render. This is caused by a decision the React team made when designing the library
  • The Ternary Operator ?: produces one of two provided outcomes based on the test condition (first operand of the operator). If the test condition is true or evaluates to true, then the second expression (after the ?) will be returned. If the test condition is false or evaluates to false, 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;