← Back to React
Lesson 8 · React Fundamentals

Conditional Rendering

Ternaries, &&, and early returns - and when each one reads best.

Beginner35 min

What you will be able to do

  • Explain what conditional rendering is, and why every real screen needs it
  • Use if and early returns above the JSX
  • Use a ternary for one of two, and && for something or nothing
  • Avoid the 0 && trap, and know why || is rarely what you want
  • Return null to render nothing from a component
  • Handle loading, error, empty, and loaded states in the right order
  • Pick the clearest technique for a given condition

The idea, in plain English

Conditional rendering means showing different UI depending on the data you have. Logged in or not, loading or loaded, admin or student, empty list or full one - almost every component you write has at least one condition.

React adds no special syntax for it. A component is a JavaScript function, so you use JavaScript: if statements and early returns above the JSX, and the ternary and && expressions inside it. The skill is choosing the one that reads best for each case.

The rule of thumb: if the whole component changes - a loading screen instead of the page - return early. If one piece changes between two options, use a ternary. If one piece is either there or not, use &&. And if a component should show nothing at all, return null.

Worked example: A loading, empty, error, and loaded state in one component.

if, above the JSX

if is a statement, so it cannot go inside the braces in JSX. It can go anywhere else in the function. Compute a value with if, store it in a variable, and render the variable: let message; if (isLoggedIn) message = "Welcome back!"; else message = "Please log in."; then return <h1>{message}</h1>.

The variable can hold JSX as well as text. That makes if the tool for choices with more than two branches - pick the element first, then drop it into the markup.

Early returns

A component can return more than once. if (!user) return <p>Loading...</p>; ends the function right there, so the JSX below it is never reached - and every line below can assume user exists.

That last part matters in TypeScript. With user typed as User | null, TypeScript knows that after the early return user is a User, so user.name compiles. Inside a big ternary you get the same narrowing, but buried in markup.

Use an early return whenever a state replaces the whole component: loading, error, not found, not allowed.

The ternary: one of two

condition ? a : b is an expression, so it fits inside braces. Both branches can be text - {isLoggedIn ? "Logout" : "Login"} - or elements - {isLoggedIn ? <Dashboard /> : <Login />}.

When a branch needs several elements, wrap them in a Fragment. When the whole return is a ternary, it can sit directly after return without an outer wrapper.

Chaining ternaries for three or more states works, but each level makes the markup harder to scan. Past two options, switch to early returns or to an if that picks a variable.

&&: something or nothing

a && b returns a when a is falsy and b otherwise. {isAdmin && <button>Admin Settings</button>} gives the button when isAdmin is true, and false when it is not - and React renders false as nothing.

Conditions can be chained: {isLoggedIn && isOwner && <button>Edit Course</button>} shows the button only when both are true.

The left side must be a real boolean. With lessonsCompleted = 0, {lessonsCompleted && <p>Course started</p>} returns 0, and React prints 0 on the page. Write {lessonsCompleted > 0 && ...} instead - Lesson 6 covers why.

Watch out: Counts and lengths are the usual culprits. {items.length && <List />} prints 0 for an empty list; {items.length > 0 && <List />} prints nothing.

Why || is rarely what you want

a || b returns a when a is truthy and b otherwise - the reverse of &&. So {isAdmin || <AdminPanel />} shows nothing to admins, because true renders as nothing, and shows the panel to everyone else. It is almost never what you meant.

|| and ?? have a real use in JSX: supplying a fallback value, not deciding whether to render. {user.nickname || user.name} shows the nickname if there is one. ?? is stricter - it only falls back on null and undefined, so {score ?? "Not attempted"} still shows a score of 0.

return null: render nothing

A component can return null to render nothing at all. function AdminButton({ isAdmin }) { if (!isAdmin) return null; return <button>Admin Settings</button>; } lets the component own its own visibility rule.

The parent then writes <AdminButton isAdmin={user.isAdmin} /> without a condition around it. Whether that is cleaner than {isAdmin && <AdminButton />} in the parent depends on whether the rule belongs to the button or to the page.

Loading, error, empty, loaded

Any component that shows fetched data has four states: still loading, failed, succeeded with nothing to show, and succeeded with data. Early returns handle them in order, one per if.

The order matters. Check loading first, because there is no data or error yet. Check error next, because a failed request has no data. Then check for empty, and only then render the real UI - which can now assume the data exists and is not empty.

You will write this shape again and again: with useEffect, with TanStack Query, with Redux, on dashboards and detail pages. Learn it once here.

Choosing a technique

Early return when a state replaces the whole component. Ternary when one piece is one of two things. && when one piece is either there or not, with a boolean on the left. if into a variable when there are three or more options. return null when a component should hide itself.

Conditions do not only decide what exists. The same expressions choose attribute values - disabled={isSaving}, className={completed ? "lesson completed" : "lesson"} - and text - {isSaving ? "Saving..." : "Save"}.

Which one to use
Whole component changesEarly return: if (loading) return <Loading />;
A or BTernary: {isLoggedIn ? <Dashboard /> : <Login />}
Something or nothing&&: {isAdmin && <AdminPanel />} - boolean on the left
Three or more optionsif into a variable, or one early return per option
Component hides itselfreturn null;
Fallback value?? or ||: {user.nickname ?? user.name}

Syntax and examples

if into a variable
function Greeting({ isLoggedIn }: { isLoggedIn: boolean }) { let message; if (isLoggedIn) { message = "Welcome back!"; } else { message = "Please log in."; } return <h1>{message}</h1>; }
Early return, and what TypeScript learns from it
type User = { name: string; email: string }; function Profile({ user }: { user: User | null }) { if (!user) { return <p>Loading...</p>; } // From here on, TypeScript knows user is a User, not null. return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ); }
The same component as a ternary - harder to scan
function Profile({ user }: { user: User | null }) { return ( <div> {!user ? ( <p>Loading...</p> ) : ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> )} </div> ); }
Ternaries: text, components, and several elements
function LoginButton({ isLoggedIn }: { isLoggedIn: boolean }) { return <button>{isLoggedIn ? "Logout" : "Login"}</button>; } function CourseStatus({ completed }: { completed: boolean }) { return completed ? ( <> <h2>Course Completed</h2> <p>Congratulations!</p> </> ) : ( <> <h2>Course In Progress</h2> <p>Keep learning.</p> </> ); }
&&, alone and chained
function CourseActions({ isLoggedIn, isOwner, }: { isLoggedIn: boolean; isOwner: boolean; }) { return ( <div> {isLoggedIn && <button>Enroll</button>} {isLoggedIn && isOwner && <button>Edit Course</button>} </div> ); }
&& and || side by side - checked in React 19
{true && <p>Panel</p>} // <p>Panel</p> {false && <p>Panel</p>} // nothing {true || <p>Panel</p>} // nothing - true renders as nothing {false || <p>Panel</p>} // <p>Panel</p> - the reverse of what you wanted {0 && <p>Started</p>} // 0 - printed on the page {0 > 0 && <p>Started</p>} // nothing
return null
function AdminButton({ isAdmin }: { isAdmin: boolean }) { if (!isAdmin) { return null; } return <button>Admin Settings</button>; } // The parent needs no condition: <AdminButton isAdmin={user.isAdmin} />
Three or more options: pick first, render once
type Role = "student" | "trainer" | "admin"; function Dashboard({ role }: { role: Role }) { let content; if (role === "student") { content = <StudentDashboard />; } else if (role === "trainer") { content = <TrainerDashboard />; } else { content = <AdminDashboard />; } return ( <main> <h1>Dashboard</h1> {content} </main> ); }
Loading, error, empty, loaded
type Course = { id: number; title: string }; type CourseListProps = { loading: boolean; error: string | null; courses: Course[]; }; function CourseList({ loading, error, courses }: CourseListProps) { if (loading) { return <p>Loading courses...</p>; } if (error) { return <p role="alert">{error}</p>; } if (courses.length === 0) { return <p>No courses available.</p>; } return ( <ul> {courses.map((course) => ( <li key={course.id}>{course.title}</li> ))} </ul> ); }
The nested ternary to avoid, and its replacement
// Works, but nobody can read it return ( <div> {loading ? <Loading /> : error ? <ErrorMessage /> : data ? (data.length ? <List data={data} /> : <Empty />) : null} </div> ); // The same logic, one state per line if (loading) return <Loading />; if (error) return <ErrorMessage />; if (!data || data.length === 0) return <Empty />; return <List data={data} />;
Conditions in attributes and text
function SaveButton({ saving }: { saving: boolean }) { return ( <button disabled={saving} className={saving ? "btn btn-busy" : "btn"}> {saving ? "Saving..." : "Save"} </button> ); } function ProgressText({ completed }: { completed: number }) { return ( <p> {completed === 0 ? "Start your course" : `${completed} ${completed === 1 ? "lesson" : "lessons"} completed`} </p> ); } // 0 -> Start your course 1 -> 1 lesson completed 5 -> 5 lessons completed

Tip: Read your early returns top to bottom as a list of reasons not to show the page yet. Whatever survives to the last return is the normal case - and can assume everything the checks ruled out.

Conditional rendering at a glance

if / else

Outside JSX. Picks a value or an element into a variable.

let content;
if (a) content = <A />;
Early return

Ends the component for a special state.

if (loading) return <Loading />;
Ternary

Inside JSX. One of two.

{ok ? <A /> : <B />}
&&

Inside JSX. Something or nothing.

{count > 0 && <Badge />}
return null

The component renders nothing.

if (!allowed) return null;
?? / ||

A fallback value, not a render decision.

{nickname ?? name}

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

Walk the states

“Render CourseList four times: loading true; error "Network error"; courses []; and two courses. Then swap the order of the loading and error checks and see what breaks.”

Catch the 0

“Write {lessonsCompleted && <p>Course started</p>} with lessonsCompleted = 0, find the stray 0 on the page, and fix it.”

Flip && to ||

“Change {isAdmin && <AdminPanel />} to ||, and render it with isAdmin true and then false. Who sees the panel now?”

Let TypeScript help

“In Profile, delete the early return. What does TypeScript say about user.name, and why?”

What usually goes wrong

Putting if inside JSX

if is a statement and braces need an expression. Use && or a ternary inside the JSX, or move the if above the return.

✗ <div>
  {if (isAdmin) { <Admin /> }}
</div>
✓ <div>
  {isAdmin && <Admin />}
</div>
A number on the left of &&

When it is 0, the expression returns 0, and React prints it.

✗ {count && <Badge />}
✓ {count > 0 && <Badge />}
Using || to show something

|| returns the left side when it is truthy, so the element appears only when the condition is false.

✗ {isAdmin || <AdminPanel />}
✓ {isAdmin && <AdminPanel />}
Nesting ternaries

Each level makes the markup harder to scan and to change.

✗ {a ? <A /> : b ? <B /> : c ? <C /> : <D />}
✓ if (a) return <A />;
if (b) return <B />;
if (c) return <C />;
return <D />;
Checking states in the wrong order

While a request is loading there is no data yet, so an empty check placed first shows "No courses" for a moment before the real list.

✗ if (courses.length === 0) return <Empty />;
if (loading) return <Loading />;
✓ if (loading) return <Loading />;
if (error) return <ErrorMessage />;
if (courses.length === 0) return <Empty />;
Untyped placeholder data

const user = null; makes TypeScript treat user as always null, so user.name fails with "Property name does not exist on type never". Type the value as it will really be.

✗ const user = null;
✓ const user: User | null = null;

Best practices

  • Handle special states with early returns, in the order loading, error, empty.
  • Use a ternary for two options and && for one; move three or more out of the JSX.
  • Keep the left side of && a real boolean.
  • Type values that can be missing as T | null, and let early returns narrow them.
  • Give every fetched list an empty state - a blank area looks like a bug.
  • Decide whether a visibility rule belongs to the component (return null) or its parent (&&).

Practice

Write these yourself before opening anything. Getting them wrong first is most of how this sticks.

1.

Write a CoursePage that takes loading, error, and courses props, and shows "Loading courses...", "Failed to load courses.", "No courses available.", or the list of course titles.

Show hint

Three early returns, then the list.

Show solution
type Course = { id: number; title: string }; function CoursePage({ loading, error, courses, }: { loading: boolean; error: boolean; courses: Course[]; }) { if (loading) { return <p>Loading courses...</p>; } if (error) { return <p>Failed to load courses.</p>; } if (courses.length === 0) { return <p>No courses available.</p>; } return ( <ul> {courses.map((course) => ( <li key={course.id}>{course.title}</li> ))} </ul> ); }
2.

For each, choose &&, a ternary, an early return, or return null: (a) a "New" badge on recent lessons, (b) a Login or Logout button, (c) a spinner while the whole page loads, (d) a PremiumBanner that hides itself for premium users.

Show hint

Ask: something or nothing, one of two, the whole component, or the component deciding for itself?

Show solution
(a) && {isNew && <Badge>New</Badge>} (b) ternary {isLoggedIn ? <LogoutButton /> : <LoginButton />} (c) early return if (loading) return <Spinner />; (d) return null inside PremiumBanner: if (isPremium) return null;
3.

Fix this component so it never shows a stray 0 and shows "1 lesson" rather than "1 lessons": {count && <p>{count} lessons completed</p>}.

Show hint

Two separate conditions - one for whether to render, one for the word.

Show solution
function Completed({ count }: { count: number }) { return ( <div> {count > 0 && ( <p> {count} {count === 1 ? "lesson" : "lessons"} completed </p> )} </div> ); }
Coding challenge

A lesson status control

Render a lesson status as a locked label, a start button, or a completed label - with no nested ternaries.

It should
  • Accept status: "locked" | "available" | "completed".
  • locked shows a span with "🔒 Lesson Locked".
  • available shows a button with "▶ Start Lesson".
  • completed shows a span with "✓ Completed".
  • Use early returns, one per status.
  • Render a list of three lessons, one in each status.
type LessonStatusProps = { status: "locked" | "available" | "completed"; }; function LessonStatus({ status }: LessonStatusProps) { // one early return per status }
Show one solution
type Status = "locked" | "available" | "completed"; function LessonStatus({ status }: { status: Status }) { if (status === "locked") { return <span>🔒 Lesson Locked</span>; } if (status === "available") { return <button>▶ Start Lesson</button>; } return <span>✓ Completed</span>; } const lessons: { id: number; title: string; status: Status }[] = [ { id: 1, title: "JSX Fundamentals", status: "completed" }, { id: 2, title: "Conditional Rendering", status: "available" }, { id: 3, title: "Rendering Lists", status: "locked" }, ]; function LessonList() { return ( <ul> {lessons.map((lesson) => ( <li key={lesson.id}> {lesson.title} <LessonStatus status={lesson.status} /> </li> ))} </ul> ); }

Key points

  • Conditional rendering is plain JavaScript - no special React syntax.
  • if belongs above the JSX; inside braces, use a ternary or &&.
  • Early returns handle states that replace the whole component.
  • A ternary picks one of two; && picks something or nothing.
  • Keep the left side of && a boolean, or a 0 appears on the page.
  • || shows its right side only when the left is falsy - rarely what you want for rendering.
  • return null renders nothing.
  • Fetched data: check loading, then error, then empty, then render.

Quick check before you move on

When is an early return better than a ternary?
When the state replaces the whole component, such as loading or error. The code below it can then assume the normal case.
What does {isAdmin || <AdminPanel />} render for an admin?
Nothing. isAdmin is true, so || returns true, which React renders as nothing.
How does a component render nothing?
It returns null.
Why check loading before empty?
While loading there is no data yet, so an empty check placed first would briefly show the empty state.

Interview questions

What is conditional rendering?

Showing different UI depending on data or state. React has no special syntax for it - you use JavaScript: if and early returns outside the JSX, ternaries and && inside it.

When do you use &&, a ternary, or an early return?

&& for something or nothing, with a boolean on the left. A ternary for one of two. An early return when a state - loading, error, not found - replaces the whole component.

Why can {count && <Badge />} be a bug?

When count is 0, && returns 0, and React renders numbers, so a stray 0 appears. Compare explicitly: {count > 0 && <Badge />}.

How do you render nothing from a component?

Return null. The component still exists in the tree, but produces no DOM.

How do you structure loading, error, and success states?

Early returns in order: loading first, then error, then empty, then the real UI. Each check rules something out, so the final return can assume the data exists.

Why avoid nested ternaries?

They work, but each level makes the markup harder to read and to change. Early returns or an if that picks a variable express the same logic one case per line.

Quiz

  1. 1.

    Which shows an Admin button only to admins? A. {isAdmin && <AdminButton />} B. {if (isAdmin) <AdminButton />} C. {isAdmin || <AdminButton />} D. {<AdminButton /> && isAdmin}

  2. 2.

    Which fits two different UIs? A. condition && <A /> B. condition ? <A /> : <B />

  3. 3.

    count is 0. What does {count && <Badge />} render? A. Nothing B. The badge C. 0 D. An error

  4. 4.

    Which is clearest for a loading state that replaces the whole component? A. if (loading) return <Loading />; B. A nested ternary around all the JSX

  5. 5.

    What does return null; do in a component? A. Throws an error B. Renders the text null C. Renders nothing D. Unmounts the parent

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...