JSX Expressions
Embedding values with braces, and what JSX refuses to render.
What you will be able to do
- Use {} to put any JavaScript expression into content or an attribute
- Tell an expression from a statement, and know why if cannot go in braces
- Predict what React shows for strings, numbers, booleans, null, and undefined
- Explain why {count && <Badge />} can print a stray 0 - and fix it
- Choose between &&, a ternary, and an early return
- Render object properties and arrays, and explain why a plain object fails
- Build dynamic attributes - src, disabled, and className
The idea, in plain English
Curly braces in JSX switch from markup back to JavaScript. Whatever is inside is evaluated, and the value it produces is placed into the element - as text, as an attribute, or as more elements.
Only expressions fit, because the braces become an argument to a function call, and a function argument must be a value. name, price * quantity, user.name, isLoggedIn ? "Logout" : "Login", and items.map(...) all produce values. if, for, and const do not, so they cannot go inside braces.
React then decides what each value looks like. Strings and numbers appear as text - including 0. true, false, null, undefined, and the empty string appear as nothing at all, which is exactly what makes conditions like {isAdmin && <AdminPanel />} work. A plain object cannot be shown and throws an error.
Nearly every bug in this lesson comes from the gap between those two rules: 0 is falsy in JavaScript, but React still prints it.
Worked example: Why false, null, and undefined print nothing but 0 prints.
Braces switch to JavaScript
In <h1>Hello name</h1>, name is just four letters of text. In <h1>Hello {name}</h1>, name is a variable: React evaluates it and shows its value.
Braces can hold any expression - a variable, a calculation such as {price * quantity}, a property such as {user.name}, a method call such as {name.toUpperCase()}, or a function call such as {formatPrice(price)}. Each one is evaluated every time the component renders.
Expressions, not statements
An expression produces a value: 10 + 20, user.name, items.length, isLoggedIn ? "Welcome" : "Please login". A statement performs an action and produces nothing you could pass along: if, for, while, switch, const.
JSX compiles braces into a function argument, so {if (isLoggedIn) { ... }} is a syntax error - the compiler reports "Expression expected." Use an expression form instead: a ternary for either-or, && for show-or-nothing, or move the if above the return.
What React shows for each value
Strings and numbers are rendered as text. That includes 0, and it includes NaN, which React prints as "NaN" and warns about in development.
true, false, null, undefined, and the empty string render nothing. React ignores booleans on purpose, so that conditions can live directly inside JSX.
Arrays are rendered item by item, so {["React", "Node"]} shows ReactNode. A plain object is not a valid child: React throws "Objects are not valid as a React child (found: object with keys {name, age})", and TypeScript rejects it before you run it.
{"React"}React{123}123{0}0 - a number, so it is shown{NaN}NaN, with a development warning{""}nothing{true} {false}nothing{null} {undefined}nothing{["React", "Node"]}ReactNode - each item in turn{<h1>Hi</h1>}the element{{ name: "Chandu" }}Error: Objects are not valid as a React childConditions with &&
a && b returns a if a is falsy, and b otherwise. So {isAdmin && <button>Admin Settings</button>} returns the button when isAdmin is true, and false when it is not - and false renders nothing.
This is the most common way to show something or nothing. It works well whenever the left side is a real boolean.
The 0 && trap
When the left side is a number, && goes wrong. With messages = 0, the expression {messages && <p>You have messages</p>} returns messages itself - the number 0 - because 0 is falsy. React shows numbers, so a stray 0 appears on the page.
Make the left side a real boolean. {messages > 0 && <p>...</p>} returns false for 0, and false renders nothing. Boolean(messages) and !!messages work too, but messages > 0 says what you mean when you are checking a count.
An empty string on the left is harmless, because React renders "" as nothing. The trap is specific to 0 - and to NaN, which is also falsy and also printed.
Watch out: Any time the left side of && is a number - a count, a length, an index - compare it explicitly. {items.length && <List />} prints 0 for an empty list.
Either-or with a ternary
condition ? a : b picks one of two values, and both can be JSX: {isLoggedIn ? <button>Logout</button> : <button>Login</button>}. Use it when there are two outcomes; use && when the other outcome is nothing.
Ternaries can be chained for three or more states, but they get hard to read quickly. When they do, compute the result above the return, split the piece into its own component, or return early.
Early returns
A component is a function, so it can return before it reaches the main JSX. if (!user) return <p>Loading...</p>; handles the special case first, and the rest of the function can assume user exists.
This is where if belongs - above the JSX, not inside the braces. It usually reads better than a ternary wrapped around the whole component.
Functions in JSX, and keeping render pure
Calling a function inside braces is normal: {formatPrice(price)}, {formatName(first, last)}. Formatting belongs in small functions, not in long expressions inside the markup.
Those functions should only calculate. React may render a component more than once - StrictMode does it on purpose in development - so a function that saves to a database or sends a request during render would do that more than once too. Side effects belong in event handlers or useEffect, later in the course.
Expressions in attributes
Braces work in attributes too: src={imageUrl}, alt={title}, disabled={isLoading}. React sets a boolean attribute when the value is true and leaves it off entirely when it is false - disabled={false} produces a button with no disabled attribute.
className often depends on state. For two options, a ternary is clearest: className={isActive ? "card active" : "card"}. A template literal such as className={`card ${isActive ? "active" : ""}`} also works, but leaves a trailing space when the condition is false.
Syntax and examples
function Order() {
const name = "Chandu";
const price = 100;
const quantity = 3;
return (
<div>
<h1>Hello {name}</h1>
<p>Total: {price * quantity}</p>
<p>{name.toUpperCase()}</p>
<p>Items: {[10, 20, 30].length}</p>
</div>
);
}
// Hello Chandu
// Total: 300
// CHANDU
// Items: 3<p>{"React"}</p>; // <p>React</p>
<p>{123}</p>; // <p>123</p>
<p>{0}</p>; // <p>0</p>
<p>{true}</p>; // <p></p>
<p>{false}</p>; // <p></p>
<p>{null}</p>; // <p></p>
<p>{undefined}</p>; // <p></p>
<p>{""}</p>; // <p></p>
<p>{["React", "Node"]}</p>; // <p>ReactNode</p>
<div>{{ name: "Chandu", age: 34 }}</div>;
// Error: Objects are not valid as a React child
// (found: object with keys {name, age})const messages = 0;
// Renders <div>0</div> - 0 && anything is 0, and React prints numbers
<div>{messages && <p>You have messages</p>}</div>;
// Renders <div></div> - the left side is now a real boolean
<div>{messages > 0 && <p>You have messages</p>}</div>;
<div>{Boolean(messages) && <p>You have messages</p>}</div>;
<div>{!!messages && <p>You have messages</p>}</div>// Error: Expression expected.
<div>
{if (isLoggedIn) { "Welcome" }}
</div>
// An expression works
<div>
{isLoggedIn ? "Welcome" : "Please login"}
</div>type User = { name: string; isAdmin: boolean };
function Dashboard({ user }: { user?: User }) {
// Early return: handle the special case before the main JSX
if (!user) {
return <p>Loading...</p>;
}
return (
<div>
<h1>Hello {user.name}</h1>
{/* Something or nothing */}
{user.isAdmin && <button>Admin Settings</button>}
{/* One of two */}
{user.isAdmin ? <p>Full access</p> : <p>Read-only access</p>}
</div>
);
}type Status = "loading" | "error" | "ready";
function StatusMessage({ status }: { status: Status }) {
// Hard to read once it grows:
// {status === "loading" ? <p>Loading...</p> : status === "error" ? <p>Error</p> : <p>Loaded</p>}
let message;
if (status === "loading") message = <p>Loading...</p>;
else if (status === "error") message = <p>Error occurred</p>;
else message = <p>Data loaded</p>;
return <div>{message}</div>;
}function formatPrice(price: number) {
return "₹" + price.toLocaleString("en-IN");
}
function formatName(firstName: string, lastName: string) {
return `${firstName} ${lastName}`;
}
function Product() {
return (
<div>
<h2>{formatName("Chandu", "Nandigam")}</h2>
<p>Price: {formatPrice(125000)}</p>
</div>
);
}
// Chandu Nandigam
// Price: ₹1,25,000const user = { name: "Chandu", age: 34 };
<div>{user.name}</div>; // Chandu
<div>{user.age}</div>; // 34
<div>{JSON.stringify(user)}</div>; // {"name":"Chandu","age":34} - for debugging
<div>{user}</div>; // Error - and a TypeScript error firstconst courses = [
{ id: 1, title: "React" },
{ id: 2, title: "Node.js" },
{ id: 3, title: "Python" },
];
function CourseList() {
return (
<ul>
{courses.map((course) => (
<li key={course.id}>{course.title}</li>
))}
</ul>
);
}const imageUrl = "/images/react.png";
const isLoading = true;
const isActive = false;
const userId = 101;
<img src={imageUrl} alt="React" />;
<button disabled={isLoading}>Submit</button>; // disabled="" (false: no attribute at all)
<div className={isActive ? "card active" : "card"}>Course</div>;
<div className={`user-${userId}`}>User</div>; // class="user-101"const completed = 1;
const total = 3;
<p>{(completed / total) * 100}%</p>; // 33.33333333333333%
<p>{Math.round((completed / total) * 100)}%</p>; // 33%Tip: Ask one question of any value inside braces: will React show it? Strings and numbers yes, booleans, null, undefined, and "" no, plain objects - error.
Watch out: TypeScript does not catch the 0 && trap - a number is a perfectly valid child. You have to spot it yourself, which is why it is such a common interview question.
Expressions you will write every day
Each of these produces a value, so each can go inside braces.
VariableShow a value.
{name}PropertyShow part of an object.
{user.name}CalculationCompute while rendering.
{price * quantity}Function callFormat a value.
{formatPrice(price)}&&Something or nothing - keep the left side boolean.
{count > 0 && <Badge />}TernaryOne of two.
{isLoggedIn ? <Logout /> : <Login />}mapOne element per item, each with a key.
{items.map((i) => <Item key={i.id} />)}AttributeAny attribute value.
<button disabled={isLoading}>Try it yourself
The code does not change. Swap the content string and the program does something else entirely.
“Render <p>{value}</p> for "React", 0, false, null, undefined, and "". Which paragraphs are empty in the Elements panel?”
“Render {count && <p>New messages</p>} with count = 5, then with count = 0. Fix it so 0 shows nothing.”
“Put {user} directly into a div. Read the TypeScript error, then the runtime error, and fix it with user.name.”
“Render <button disabled={isLoading}> with isLoading true and then false, and inspect the button each time. Does the attribute disappear or become "false"?”
What usually goes wrong
Without braces, a variable name is only text.
✗ <h1>name</h1>✓ <h1>{name}</h1>if is a statement, so the compiler reports "Expression expected." Use &&, a ternary, or an if above the return.
✗ {if (isLoggedIn) { return <Dashboard />; }}✓ {isLoggedIn ? <Dashboard /> : <Login />}When the number is 0, the expression returns 0 and React prints it.
✗ {count && <Badge />}✓ {count > 0 && <Badge />}A plain object is not a valid React child. Pick the property you want to show.
✗ <div>{user}</div>✓ <div>{user.name}</div>Render can run more than once, so anything with a side effect would run more than once too.
✗ function Profile() {
saveVisit();
return <h1>Profile</h1>;
}✓ // call saveVisit() from an event handler or useEffectReact needs a stable key on each item a map returns. Lesson 9 covers why.
✗ {lessons.map((l) => <p>{l.title}</p>)}✓ {lessons.map((l) => <p key={l.id}>{l.title}</p>)}Best practices
- Keep the left side of && a real boolean - compare counts and lengths explicitly.
- Use && for something-or-nothing and a ternary for one-of-two.
- Move complex conditions above the return, or into an early return.
- Put formatting in small, pure functions and call them from JSX.
- Never cause side effects while rendering.
- Round or format computed numbers before showing them.
Practice
Write these yourself before opening anything. Getting them wrong first is most of how this sticks.
Build a LessonCard for { title: "JSX Expressions", duration: 30, completed: true, quizQuestions: 10 } that shows the title, "Duration: 30 minutes", "Quiz: 10 questions", and Completed or Not Completed.
Show hintHide hint
Two outcomes means a ternary.
Show solutionHide solution
const lesson = {
title: "JSX Expressions",
duration: 30,
completed: true,
quizQuestions: 10,
};
function LessonCard() {
return (
<article>
<h2>{lesson.title}</h2>
<p>Duration: {lesson.duration} minutes</p>
<p>Quiz: {lesson.quizQuestions} questions</p>
{lesson.completed ? <span>Completed</span> : <span>Not Completed</span>}
</article>
);
}Predict what each line renders, then check: {0 && "a"}, {"" && "b"}, {null ?? "c"}, {[1, 2, 3]}, {5 > 3 && "d"}.
Show hintHide hint
Work out the value of each expression first, then ask whether React shows that value.
Show solutionHide solution
{0 && "a"} -> 0 (the value is 0, a number)
{"" && "b"} -> nothing ("" renders nothing)
{null ?? "c"} -> c (?? falls through on null)
{[1, 2, 3]} -> 123 (arrays render item by item)
{5 > 3 && "d"} -> dA Notifications component shows "{count} notifications" inside a span, but a lone 0 appears when there are none. Fix it.
Show hintHide hint
What does 0 && anything return?
Show solutionHide solution
function Notifications({ count }: { count: number }) {
return (
<div>
{count > 0 && <span>{count} notifications</span>}
</div>
);
}A course progress card
Show progress for any number of completed lessons, without ever printing a stray 0.
- Take completedLessons and totalLessons as numbers.
- Always show "X / Y lessons completed".
- Show a rounded percentage.
- Show "Course Started" only when at least one lesson is complete.
- Show "Course Complete" instead when every lesson is complete.
- Check it with 0, 1, and 10 completed out of 10.
function CourseProgress() {
const completedLessons = 0;
const totalLessons = 10;
return (
<div>
{/* your JSX here */}
</div>
);
}Show one solutionHide solution
function CourseProgress({
completedLessons,
totalLessons,
}: {
completedLessons: number;
totalLessons: number;
}) {
const percent = Math.round((completedLessons / totalLessons) * 100);
const isComplete = completedLessons === totalLessons;
return (
<div>
<p>
{completedLessons} / {totalLessons} lessons completed ({percent}%)
</p>
{isComplete ? (
<span>Course Complete</span>
) : (
completedLessons > 0 && <span>Course Started</span>
)}
</div>
);
}
// 0 of 10 -> "0 / 10 lessons completed (0%)" nothing else - not a stray 0
// 1 of 10 -> "1 / 10 lessons completed (10%)" Course Started
// 10 of 10 -> "10 / 10 lessons completed (100%)" Course CompleteKey points
- Braces hold one JavaScript expression - anything that produces a value.
- Statements such as if and for cannot go inside braces; use &&, a ternary, or an early return.
- Strings and numbers render, including 0 and NaN.
- true, false, null, undefined, and "" render nothing.
- A plain object is not a valid child; show one of its properties.
- a && b returns a when a is falsy, so a numeric 0 on the left is printed.
- Compare counts explicitly: {count > 0 && <Badge />}.
- Braces work in attributes; a false boolean attribute is left off entirely.
- Functions called during render should only calculate, never cause side effects.
Quick check before you move on
Interview questions
What is a JSX expression?
A JavaScript expression inside curly braces in JSX. It is evaluated on every render and its value becomes a child or an attribute value.
Can you use if inside JSX?
Not inside braces - if is a statement and braces need a value. Use && or a ternary inside the JSX, or put the if above the return, including as an early return.
Why does 0 render but false does not?
React deliberately renders booleans, null, and undefined as nothing so conditions can be written inline. 0 is a number, and numbers are always rendered.
What is wrong with {count && <Badge />}?
When count is 0, && returns 0, which React renders as the text 0. Make the left side a boolean: {count > 0 && <Badge />}.
Can you render an object directly?
No. React throws "Objects are not valid as a React child". Render a property, map an array of objects into elements, or use JSON.stringify for debugging.
Why should render logic be free of side effects?
React can call a component more than once - StrictMode does in development, and concurrent rendering can discard renders. Side effects in render would repeat or happen for UI that never appears. They belong in event handlers or effects.
&& or a ternary - when do you use each?
&& when the alternative is nothing, as long as the left side is a real boolean. A ternary when there are two things to show. For more than two, compute the result before the return or split the component.
Quiz
- 1.
const value = 0; return <div>{value}</div>; What renders? A. Nothing B. 0 C. undefined D. An error
- 2.
const value = false; return <div>{value}</div>; What renders? A. false B. 0 C. Nothing D. An error
- 3.
Which is a valid JSX expression? A. {if (user)} B. {user.name} C. {for (...)} D. {const x = 10}
- 4.
Which renders the badge only when count is above zero, without a stray 0? A. {count && <Badge />} B. {count > 0 && <Badge />}
- 5.
const user = { name: "Chandu" }; return <div>{user}</div>; What happens? A. It shows Chandu B. It shows [object Object] C. An error: objects are not valid as a React child D. Nothing
- 6.
What does {"" && <p>Hi</p>} render? A. Hi B. An empty string - nothing visible C. 0 D. An error
Comments
Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.
Loading comments...
AI
System Design
Backend
- GraphQL8 modules · 69 lessons planned
- Core Python13 modules · 75 lessons planned
- FastAPI5 sections · 20 lessons
- Node.js14 modules · 206 lessons planned
- Node.js Performance7 chapters · 36 topics
- Event Loop Lifecycle6 phases · 3 scenarios
- Docker & Containerization11 modules · 144 lessons planned
- AWS for Developers14 modules · 219 lessons planned
- CI/CD & DevOps Automation10 modules · 134 lessons planned