React Basics #7: Conditional Rendering

6 min read

Last time we covered event handling and saw a glimpse of expressions like {lastSubmitted && ...} at the end. This time we’ll round up the patterns for conditional rendering — drawing the screen differently based on state.

What is conditional rendering? #

Showing different menus depending on whether the user is logged in, displaying a loading indicator while data is being fetched, or showing an error message when input validation fails — every app does this. Rendering different JSX based on some condition is what we call conditional rendering.

React doesn’t have a special syntax for this. You just use JavaScript’s conditional expressions. Let’s look at the four most common patterns.

Pattern 1. Branching with if — early return #

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

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

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

export default Greeting;

If user is missing, 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’s readable when the branches are large.

Pattern 2. Ternary operator — picking between two in JSX #

When you need to choose between two things 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’s curly braces? An if statement is a statement, so it can’t go there, but the ternary operator is an expression, so it can.

You can also use it to split JSX itself into two branches.

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

When the ternary gets long, readability drops, so for big chunks of JSX it’s better to use Pattern 1 (early return) or to split things into variables.

Pattern 3. The && operator — show or hide #

The most common case is “show this only when the condition is true; show nothing otherwise.” && fits this nicely.

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

export default Notification;

In JavaScript, A && B returns B if A is truthy and A if it’s falsy. If unreadCount > 0 is true, <p>...</p> ends up there; if it’s false, false ends up there. React doesn’t render anything for false, null, or undefined, so it looks like nothing is there.

A pitfall with && — the number 0 #

There’s a common pitfall with &&. Look at this code.

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

When count is 0, the intent is “show nothing,” but in reality the screen displays the number 0. That’s 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 convert it to a boolean.

fixed code
{count > 0 && <p>{count} items in your cart.</p>}

count > 0 is always true or false, so it’s safe. Get into the habit of putting a clear boolean on the left side of && and you won’t fall into this trap.

Pattern 4. Returning null — don’t render the component at all #

When the entire component should disappear from the screen if a condition isn’t 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 at the component’s position. From the parent’s perspective, you can always write <Banner message={...} />, and Banner itself decides whether to appear. Clean.

Storing JSX in a variable #

When the branching gets complex, it reads better to 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 store it in an object.

Pattern summary #

SituationRecommended pattern
The branch decides the entire JSX of the componentearly return (if)
Pick one of two things to showternary operator (A ? B : C)
Show only when the condition is true&& operator (boolean on the left)
Hide the component entirelyreturn null
Three or more branches, or complex branchingstore JSX in a variable

The key point is that there’s no special React syntax — you just use JavaScript’s conditional expressions inside JSX.

Try it yourself #

Let’s extend the MessageForm from last article a bit: “show a warning if the name is empty,” “show a warning if the message is empty,” and “submit only when both are filled in.”

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 submitted: <strong>{lastSubmitted.name}</strong>  {lastSubmitted.message}
        </p>
      ) : (
        <p style={{ marginTop: '12px', color: '#888' }}>
          No messages submitted yet.
        </p>
      )}
    </div>
  );
}

export default MessageForm;

Conditional rendering patterns used here:

  • name.length === 0 && <span>...</span> — show a warning only when nothing is typed (&&)
  • disabled={!isValid} — enable/disable the button based on validity
  • isValid ? 'Add' : 'Please complete the form' — branch the button label with a ternary
  • lastSubmitted ? <p>...</p> : <p>...</p> — branch the info message with a ternary

Multiple patterns naturally mix in one screen. Type around in the form and observe how the screen reacts.

Wrapping up #

In this article we looked at the four conditional rendering patterns (early return, ternary, &&, returning null) and the variable trick. There’s no single right answer — what matters is the sense to pick the most readable pattern for the situation. And just dodge the number 0 pitfall when using &&.

So far the data we’ve drawn has been limited to one or two values. But real apps have to render many pieces of data at once — lists of posts, products, notifications. In the next article, “React Basics #8: Lists and key,” we’ll cover how to render arrays on screen and the meaning of the special prop key that always shows up alongside.

X