Contents
7 Chapter

Conditional rendering

Patterns for splitting the UI with if · ternary · && · null returns, plus common traps — especially the trap of the number 0 on the left of `&&`.

At the end of Chapter 6 we briefly saw an expression like {lastSubmitted && ...}. In this chapter we sort out conditional rendering patterns for drawing the screen differently based on state.

What is conditional rendering? #

Showing a different menu based on login state, displaying a loading indicator while data is fetching, or surfacing an error message when input validation fails — all of these happen in every app. Rendering different JSX based on some condition is called conditional rendering.

React does not have any special syntax for this; it uses JavaScript’s conditional expressions as-is. Let us look at the four patterns you will use most often.

Pattern 1. Branch with if (early return) #

The most straightforward approach is to branch with if inside the component function and return different JSX.

src/Greeting.jsx
function Greeting({ user }) {
  if (!user) {
    return <p>Please log in.</p>;
  }

  return <h1>Hello, {user.name}!</h1>;
}

export default Greeting;

If user is absent, return the login prompt and the function ends. The second return only runs when user exists. This style of exiting a function early is called early return, and it reads well when the branches are large.

Pattern 2. Ternary operator (two-way pick inside JSX) #

When you need to pick one of two things right in the middle of JSX, use JavaScript’s ternary operator (condition ? A : B).

src/LoginButton.jsx
function LoginButton({ isLoggedIn }) {
  return (
    <button>
      {isLoggedIn ? 'Log out' : 'Log in'}
    </button>
  );
}

export default LoginButton;

Remember that only expressions can go inside JSX curly braces? if is a statement and cannot, but the ternary is an expression and can.

It also works for splitting JSX itself two ways.

src/UserStatus.jsx
function UserStatus({ user }) {
  return (
    <div>
      {user ? (
        <p>Hello, {user.name}!</p>
      ) : (
        <p>Please log in.</p>
      )}
    </div>
  );
}

But as ternaries grow, readability drops. When the JSX chunks get large, Pattern 1 (early return) or pulling JSX into a variable reads better.

Pattern 3. && (show or don’t show) #

The most common case is “show something only when the condition is true, otherwise show nothing.” For that, the && operator is a good fit.

src/Notification.jsx
function Notification({ unreadCount }) {
  return (
    <div>
      <h2>Notifications</h2>
      {unreadCount > 0 && (
        <p>You have {unreadCount} unread message(s).</p>
      )}
    </div>
  );
}

export default Notification;

In JavaScript, A && B returns B when A is truthy and A when A is falsy. When unreadCount > 0 is true, <p>...</p> lands in that spot; when false, false lands there. React renders nothing for false, null, and undefined, so it ends up looking as if the element disappeared.

The && trap — the number 0 #

There is a common trap with &&. Look at the following code.

problematic code
function Cart({ count }) {
  return (
    <div>
      {count && <p>{count} item(s) in your cart.</p>}
    </div>
  );
}

When count is 0, the intent is “show nothing,” but in practice the number 0 itself shows up on the screen. That is because 0 && X returns 0, and React really does render the number 0 (only false, null, and undefined are skipped).

The fix is to explicitly coerce to a boolean.

fixed code
{count > 0 && <p>{count} item(s) in your cart.</p>}

count > 0 is always true or false, which is safe. Get in the habit of putting a clear boolean on the left of && and you will not fall into this trap. The empty string '' has the same problem, so instead of name && ..., write something explicit like name.length > 0 && ....

Pattern 4. Return null (don’t render the component at all) #

When the component itself should disappear from the screen if a condition is not met, return null.

src/Banner.jsx
function Banner({ message }) {
  if (!message) return null;

  return (
    <div style={{ background: '#fffbcc', padding: '12px' }}>
      {message}
    </div>
  );
}

export default Banner;

When you return null, React renders nothing in that spot. From the parent’s perspective, you always write <Banner message={...} /> and the decision of whether to show is left to Banner itself. Clean.

Storing JSX in a variable #

When the branching gets complex, reading improves if you store JSX in a variable and use it.

src/Page.jsx
function Page({ status, data, error }) {
  let content;

  if (status === 'loading') {
    content = <p>Loading...</p>;
  } else if (status === 'error') {
    content = <p style={{ color: 'red' }}>Error: {error}</p>;
  } else {
    content = <p>Data: {data}</p>;
  }

  return (
    <div>
      <h1>Page title</h1>
      {content}
    </div>
  );
}

JSX is just a JavaScript value, so you can put it in a variable, return it from a function, or place it inside an object.

Patterns at a glance #

SituationRecommended pattern
The branch result is the component’s whole JSXearly return (if)
You want to show one of two thingsternary (A ? B : C)
You want to show something only when true&& (boolean on the left)
The component should not appear at allreturn null
Three or more branches, or it gets complexstore JSX in a variable

The key point is that React does not have a special syntax — you use JavaScript’s conditional expressions as-is inside JSX.

Try it yourself #

Let us extend the MessageForm from Chapter 6: show a warning if the name is empty, show a warning if the message is empty, and only allow submission when both are valid.

src/MessageForm.jsx
import { useState } from 'react';

function MessageForm() {
  const [name, setName] = useState('');
  const [message, setMessage] = useState('');
  const [lastSubmitted, setLastSubmitted] = useState(null);

  const isValid = name.length > 0 && message.length > 0;

  function handleSubmit(e) {
    e.preventDefault();
    if (!isValid) return;
    setLastSubmitted({ name, message });
    setName('');
    setMessage('');
  }

  return (
    <div style={{ padding: '16px', border: '1px solid #ccc', borderRadius: '8px' }}>
      <form onSubmit={handleSubmit}>
        <div>
          <input
            type="text"
            placeholder="Name"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
          {name.length === 0 && (
            <span style={{ color: 'red', marginLeft: '8px' }}>Please enter a name</span>
          )}
        </div>
        <div style={{ marginTop: '8px' }}>
          <input
            type="text"
            placeholder="Message"
            value={message}
            onChange={(e) => setMessage(e.target.value)}
          />
          {message.length === 0 && (
            <span style={{ color: 'red', marginLeft: '8px' }}>Please enter a message</span>
          )}
        </div>
        <button type="submit" disabled={!isValid} style={{ marginTop: '8px' }}>
          {isValid ? 'Add' : 'Please complete the form'}
        </button>
      </form>
      {lastSubmitted ? (
        <p style={{ marginTop: '12px' }}>
          Last submission: <strong>{lastSubmitted.name}</strong>. {lastSubmitted.message}
        </p>
      ) : (
        <p style={{ marginTop: '12px', color: '#888' }}>
          No message submitted yet.
        </p>
      )}
    </div>
  );
}

export default MessageForm;

Conditional rendering patterns used here:

  • name.length === 0 && <span>...</span>&& shows the warning only when the input is empty
  • disabled={!isValid} — enable / disable the button based on validity
  • isValid ? 'Add' : 'Please complete the form' — ternary for switching button text
  • lastSubmitted ? <p>...</p> : <p>...</p> — ternary for the status message

An example where several patterns mix naturally in one screen.

Exercises #

  1. Build a Page component that renders differently based on a status prop ('loading' / 'error' / 'success'). 'loading' shows “Loading”, 'error' shows a red error message, 'success' shows the data. Write it three ways — early return / ternary / variable-with-JSX — and compare which reads most easily.
  2. Hit the && trap yourself. In a counter starting from 0, use {count && <p>Cart: {count}</p>}. Initially the screen shows 0 against your intent. Then fix it with count > 0 && ... to escape the trap.
  3. Build a Banner component that shows a yellow box when message is given and returns null otherwise. Always render it in the parent as <Banner message={errorMessage} />, and confirm that it naturally appears and disappears as errorMessage state oscillates between empty and not.

In one line: Conditional rendering in React is just JavaScript’s conditional expressions. early return / ternary / && / null return / JSX in a variable — pick from the five patterns based on the situation. Keep a clear boolean on the left of && to avoid the number 0 trap.

Next chapter #

So far the data we show has been limited to one or two pieces. In real apps we have to render multiple pieces of data at once — a post list, a product list, a notification list. In the next chapter, Chapter 8: Lists and key, we cover how to render an array on screen, and the meaning of a special prop, key, that inevitably appears. This becomes the foundation for the reconciliation algorithm discussion in Chapter 14 (Performance).

X