React 6 ⚛️ Embedding JavaScript Expressions in JSX
JSX looks like HTML, but it is JavaScript. The angle brackets and tag syntax are a convenience that the compiler transforms into function calls, and inside those tags you can embed any JavaScript expression using curly braces. This is the mechanism that makes JSX dynamic: it is how variables, function calls, arithmetic, ternaries, and method results become part of the rendered output. Understanding where braces are allowed, what counts as an expression, and what does not, is the difference between JSX that works and JSX that fails to compile.
The braces are not a template language in the sense of Handlebars or Jinja. They do not introduce loops or conditionals as statements. They evaluate a single expression and insert its result. Loops become array methods like map, and conditionals become ternary operators or logical &&. This constraint is deliberate: JSX stays within the expression grammar of JavaScript, which keeps it predictable and analyzable.
This chapter covers the curly brace syntax, which JavaScript constructs can appear inside them, the distinction between expressions and statements, embedding in attributes versus children, the handling of different value types, and the patterns that replace statement-based logic.
Key point: Curly braces in JSX embed a single JavaScript expression. Expressions produce values; statements do not. Loops and conditionals become map, ternaries, and logical operators because those produce values that JSX can render.
Why expression embedding exists
The static markup problem. If JSX could only contain literal text and literal attribute values, it would be no more useful than an HTML file. Every dynamic value — a user’s name, a computed total, a conditional class — would require manual string concatenation or DOM manipulation outside the JSX. Embedding expressions lets dynamic values live directly in the markup where they are rendered.
The expression-versus-statement problem. JavaScript distinguishes expressions (which produce a value) from statements (which perform an action). a + b is an expression; if (x) { ... } is a statement. JSX positions — between tags, inside attributes — are value positions. Only expressions are valid there. This is why {if (x) ...} is a syntax error but {x ? a : b} works.
The composition problem. Expressions compose. An expression can contain another expression, a function call, a method chain, or a conditional. This compositionality means JSX can express complex rendering logic without leaving the markup. {users.filter(u => u.active).map(u => <li>{u.name}</li>)} is a single expression that produces a list.
The predictability problem. By restricting braces to expressions, JSX avoids the ambiguities of template languages that mix statements and output. Every brace-delimited region is an expression, evaluated once, producing a value that is inserted. The rules are consistent, and the compiler can analyze the structure without executing it.
The type safety problem. In TypeScript, the expression inside braces is type-checked. A function that returns the wrong type, a property that does not exist, or a value that cannot be rendered produces a compile error. This catches errors that template languages would surface only at runtime.
a. The curly brace syntax
Curly braces in JSX delimit a JavaScript expression that is evaluated and inserted into the output.
const name = "Alice";
const element = <h1>Hello, {name}!</h1>;
// renders: <h1>Hello, Alice!</h1>
The braces can appear in two positions: as children between tags, and as attribute values.
// As children
<p>{2 + 3}</p> // renders: <p>5</p>
<p>{name.toUpperCase()}</p> // renders: <p>ALICE</p>
<p>{isLoggedIn ? "Yes" : "No"}</p> // renders: <p>Yes</p> or <p>No</p>
// As attribute values
<img src={user.avatarUrl} alt={user.name} />
<div className={isActive ? "active" : "inactive"} />
<button disabled={isLoading}>Submit</button>
In both positions, the braces contain an expression. The expression is evaluated during render, and its result is used in the output.
b. What counts as an expression
Any JavaScript expression is valid inside braces. This includes:
- Literals:
{42},{"text"},{true} - Variables:
{name},{user.email} - Arithmetic:
{price * quantity},{count + 1} - Comparison:
{age >= 18} - Logical:
{isValid && "Valid"},{a || b} - Ternary:
{isLoading ? "Loading..." : "Done"} - Function calls:
{formatDate(createdAt)},{Math.max(a, b)} - Method calls:
{name.trim().toLowerCase()} - Member access:
{user.address.city} - Array access:
{items[0]} - Template literals:
{`Hello, ${name}`} - Array methods:
{items.map(item => <li>{item}</li>)} - Object literals in attributes:
<div style={{ color: "red" }}>
The last example, a double brace, is not a special syntax. The outer brace is the JSX expression delimiter; the inner brace is the object literal.
c. What is not an expression
Statements are not expressions and cannot appear inside braces. This includes if, for, while, switch, var/let/const declarations, and return.
// Invalid: if is a statement
<div>{if (isLoggedIn) { return "Welcome"; }}</div>
// Valid: ternary is an expression
<div>{isLoggedIn ? "Welcome" : "Please log in"}</div>
// Invalid: for is a statement
<ul>{for (let i = 0; i < items.length; i++) { <li>{items[i]}</li> }}</ul>
// Valid: map is an expression
<ul>{items.map(item => <li key={item.id}>{item}</li>)}</ul>
The constraint is not arbitrary. JSX positions are value positions, and only expressions produce values. Statements perform actions, and an action does not have a value to insert.
A common confusion is with comments. Inside JSX, // comments are not valid; use {/* comment */} instead.
<div>
{/* This is a valid JSX comment */}
{name}
</div>
d. Embedding in attributes versus children
Attribute positions accept expressions, but with a difference: when the value is a string literal, no braces are needed; when it is a variable or expression, braces are required.
// Literal string: no braces
<a href="/about">About</a>
// Variable: braces required
<a href={aboutUrl}>About</a>
// Expression: braces required
<img width={size * 2} height={size * 2} />
An attribute with no value is a boolean true:
<input disabled />
// equivalent to <input disabled={true} />
When using a JavaScript object as an attribute value, the outer braces are the JSX expression and the inner braces are the object literal:
<div style={{ color: "red", fontSize: 14 }} />
When spreading an object’s properties as attributes, use the spread syntax inside braces:
const props = { id: "email", type: "email", placeholder: "Enter email" };
<input {...props} />
e. Rendering different value types
The result of the expression is converted to a string or, in some cases, rendered as a React element. The rules are:
- Strings and numbers are rendered as text.
- Booleans,
null, andundefinedrender nothing. - React elements are rendered as elements.
- Arrays of elements are rendered as a sequence.
- Objects throw an error if used as a child.
<p>{0}</p> // renders: <p>0</p>
<p>{false}</p> // renders: <p></p>
<p>{null}</p> // renders: <p></p>
<p>{undefined}</p> // renders: <p></p>
<p>{[1, 2, 3]}</p> // renders: <p>123</p>
<p>{<span>Hi</span>}</p> // renders: <p><span>Hi</span></p>
<p>{{ a: 1 }}</p> // error: objects are not valid as a child
The fact that false, null, and undefined render nothing is what makes the && pattern work:
{isLoading && <Spinner />}
When isLoading is false, the expression evaluates to false, which renders nothing. When it is true, the expression evaluates to the <Spinner /> element. This is idiomatic conditional rendering in JSX.
One caveat: if the left side of && is a number, 0 will render as 0 rather than nothing.
{count && <Badge count={count} />}
// When count is 0, renders "0", not nothing
To avoid this, use a ternary or an explicit comparison:
{count > 0 && <Badge count={count} />}
{count ? <Badge count={count} /> : null}
f. Replacing statements with expressions
Because statements are not allowed, the patterns that would use them must be expressed differently.
Conditional rendering uses the ternary operator or && instead of if:
// if/else as ternary
{isLoggedIn ? <Dashboard /> : <Login />}
// if without else as &&
{hasError && <ErrorMessage />}
Loops use map or filter instead of for:
// for loop as map
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
// filtering and mapping
<ul>
{items.filter(item => item.active).map(item => <li key={item.id}>{item.name}</li>)}
</ul>
Variable declarations happen before the JSX, not inside it:
// Invalid inside JSX
// {const x = 5; <p>{x}</p>}
// Valid: declare outside
const x = 5;
return <p>{x}</p>;
Complex logic is extracted into helper functions or variables:
const renderStatus = () => {
if (isLoading) return <Spinner />;
if (error) return <Error message={error} />;
return <Content data={data} />;
};
return <div>{renderStatus()}</div>;
The function renderStatus contains statements, but it is a function call — an expression — when used in JSX. This is the common escape hatch: move statements into a function, then call the function in the expression position.
g. Keys, fragments, and special attributes
When mapping over an array to produce a list of elements, each element needs a key prop. The key is not part of the expression syntax; it is a React requirement for identifying elements in a list.
{items.map(item => <li key={item.id}>{item.name}</li>)}
A fragment is a way to return multiple elements without a wrapping DOM node. The short syntax is <>...</>:
<>
<Header />
<Main />
<Footer />
</>
The longer syntax <React.Fragment> is equivalent and allows a key prop when mapping:
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
Certain attributes have special names in JSX: className instead of class, htmlFor instead of for, onClick instead of onclick. These are converted to the correct DOM attribute names when the element is rendered. The expression embedding rules apply to them the same way:
<label htmlFor="email">Email</label>
<button className={isPrimary ? "btn-primary" : "btn"} onClick={handleClick}>
Submit
</button>
Complete Example Session
// ============================================
// PART 1: BASIC EXPRESSION EMBEDDING
// ============================================
const name = "Alice";
function Greeting() {
return <h1>Hello, {name}!</h1>;
}
// ============================================
// PART 2: ARITHMETIC AND METHOD CALLS
// ============================================
const price = 19.99;
const quantity = 3;
function Total() {
return (
<p>
Total: {price * quantity} ({quantity} items)
</p>
);
}
// ============================================
// PART 3: TERNARY FOR CONDITIONAL RENDERING
// ============================================
function Status({ isLoading }) {
return (
<div>
{isLoading ? <p>Loading...</p> : <p>Content loaded</p>}
</div>
);
}
// ============================================
// PART 4: LOGICAL AND FOR OPTIONAL RENDERING
// ============================================
function Notification({ message }) {
return (
<div>
{message && <p className="notification">{message}</p>}
</div>
);
}
// ============================================
// PART 5: MAPPING AN ARRAY TO ELEMENTS
// ============================================
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
);
}
// ============================================
// PART 6: FILTERING BEFORE MAPPING
// ============================================
function ActiveUsers({ users }) {
return (
<ul>
{users
.filter(user => user.active)
.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// ============================================
// PART 7: EXPRESSIONS IN ATTRIBUTES
// ============================================
function Avatar({ user, size }) {
return (
<img
src={user.avatarUrl}
alt={user.name}
width={size * 2}
height={size * 2}
className={user.online ? "avatar online" : "avatar"}
/>
);
}
// ============================================
// PART 8: OBJECT LITERAL IN STYLE
// ============================================
function HighlightedText({ children, color }) {
return (
<span style={{ color: color, fontWeight: "bold" }}>
{children}
</span>
);
}
// ============================================
// PART 9: HELPER FUNCTION FOR COMPLEX LOGIC
// ============================================
function StatusPanel({ isLoading, error, data }) {
const renderContent = () => {
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage message={error} />;
return <DataView data={data} />;
};
return <div className="panel">{renderContent()}</div>;
}
// ============================================
// PART 10: AVOIDING THE ZERO PITFALL
// ============================================
function ItemCount({ count }) {
return (
<div>
{/* Wrong: renders "0" when count is 0 */}
{count && <span>{count} items</span>}
{/* Correct: explicit comparison */}
{count > 0 && <span>{count} items</span>}
{/* Also correct: ternary with null */}
{count ? <span>{count} items</span> : null}
</div>
);
}
These ten parts cover the range of expression embedding: simple variables, arithmetic, ternaries, logical &&, array mapping with keys, filtering, attribute expressions, object literals in style, helper functions for complex logic, and the zero pitfall. Each pattern is an expression, and each replaces what would be a statement in imperative code.
Quick Reference
Expression Positions
| Position | Syntax | Example |
|---|---|---|
| Children | {expression} | <p>{name}</p> |
| Attribute | attr={expression} | <img src={url} /> |
| Boolean attr | attr | <input disabled /> |
| Object value | attr={{ ... }} | <div style={{ color: "red" }} /> |
| Spread | {...props} | <input {...props} /> |
| Comment | {/* comment */} | Inside JSX only |
Valid Expression Types
| Type | Example |
|---|---|
| Variable | {name} |
| Member access | {user.email} |
| Arithmetic | {a + b} |
| Comparison | {age >= 18} |
| Logical | {a && b} |
| Ternary | {x ? y : z} |
| Function call | {format(x)} |
| Method chain | {name.trim().toUpperCase()} |
| Array map | {items.map(...)} |
| Template literal | {`Hi ${name}`} |
Rendered Value Types
| Value | Renders As |
|---|---|
| String | Text |
| Number | Text |
true / false | Nothing |
null / undefined | Nothing |
| React element | Element |
| Array of elements | Sequence |
| Object | Error |
Statement Replacements
| Statement | Expression |
|---|---|
if | Ternary ? : |
if without else | && |
for loop | .map() |
for with filter | .filter().map() |
switch | Nested ternaries or helper |
| Variable declaration | Declare before JSX |
Best Practices
✅ Do This:
{name} // Simple variable
{isLoading ? <Spinner /> : <Content />} // Ternary
{hasError && <Error />} // Logical and
{items.map(i => <li key={i.id}>{i.name}</li>)} // Map with key
const render = () => { if (x) return <A />; return <B />; };
{render()} // Helper function
❌ Don’t Do This:
{if (x) { return <A />; }} // ❌ Statement in JSX
{for (let i = 0; i < n; i++) {}} // ❌ Statement in JSX
{// comment} // ❌ Wrong comment syntax
{count && <Badge />} // ❌ Renders "0" when count is 0
{items.map(i => <li>{i.name}</li>)} // ❌ Missing key
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Statement in braces | if, for used directly | Use ternary, &&, or map |
| Zero rendered unexpectedly | && with number 0 | Use count > 0 && or ternary |
| Missing key in list | map without key prop | Add key={item.id} |
| Wrong comment syntax | // or /* */ inside JSX | Use {/* */} |
| Object as child | Object literal rendered directly | Render a property or stringify |
| Double braces confusion | Object literal in attribute | Outer is JSX, inner is object |
| Function not invoked | {renderContent} without () | Add () to call it |
Real-World Examples
1. User Greeting
<h1>Welcome back, {user.firstName}!</h1>
2. Price with Currency
<span>${(price * 1.08).toFixed(2)}</span>
3. Conditional Class
<div className={isActive ? "tab active" : "tab"}>{label}</div>
4. List of Items
<ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>
5. Filtered List
{products.filter(p => p.inStock).map(p => <Card key={p.id} product={p} />)}
6. Loading and Error States
{isLoading ? <Spinner /> : error ? <Error /> : <Content />}
7. Dynamic Attribute
<a href={`/users/${user.id}`}>{user.name}</a>
8. Inline Style Object
<div style={{ backgroundColor: theme.bg, color: theme.fg }} />
9. Spread Props
<Input {...fieldProps} value={value} onChange={handleChange} />
10. Helper Function for Branching
const renderStatus = () => {
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <Data data={data} />;
};
return <div>{renderStatus()}</div>;
Visual
Expression Positions in JSX
┌──────────────────────────────────────────────────────────────┐
│ WHERE CURLY BRACES CAN APPEAR │
│ │
│ <div className={isActive ? "on" : "off"}> │
│ │ │ │
│ │ └── Attribute expression │
│ │ │
│ │ Hello, {name}! │
│ │ │ │
│ │ └── Child expression │
│ │ │
│ │ {items.map(i => <li key={i.id}>{i.name}</li>)} │
│ │ │ │
│ │ └── Expression producing an array of elements │
│ │ │
│ └── Element │
│ │
│ Double braces in style={{ }}: │
│ └── Outer = JSX expression, inner = object literal │
└──────────────────────────────────────────────────────────────┘
Expression vs Statement
┌──────────────────────────────────────────────────────────────┐
│ EXPRESSIONS PRODUCE VALUES; STATEMENTS DO NOT │
│ │
│ EXPRESSIONS (valid in JSX): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ a + b │ │
│ │ isActive ? "yes" : "no" │ │
│ │ items.map(i => <li>{i}</li>) │ │
│ │ formatName(user) │ │
│ │ x && y │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ STATEMENTS (invalid in JSX): │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ if (x) { ... } │ │
│ │ for (let i = 0; ...) { ... } │ │
│ │ while (x) { ... } │ │
│ │ switch (x) { ... } │ │
│ │ const y = 5; │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ To use a statement: put it in a function, then call the │
│ function in the expression position. │
└──────────────────────────────────────────────────────────────┘
Rendered Value Types
┌──────────────────────────────────────────────────────────────┐
│ WHAT HAPPENS TO EACH VALUE TYPE │
│ │
│ {"text"} ──▶ renders as text │
│ {42} ──▶ renders as "42" │
│ {true} ──▶ renders nothing │
│ {false} ──▶ renders nothing │
│ {null} ──▶ renders nothing │
│ {undefined} ──▶ renders nothing │
│ {<span>Hi</span>} ──▶ renders the element │
│ {[1, 2, 3]} ──▶ renders "123" │
│ {{ a: 1 }} ──▶ ERROR: objects not valid as child │
│ │
│ The "renders nothing" behavior is what makes │
│ {condition && <Element />} work. │
└──────────────────────────────────────────────────────────────┘
The Zero Pitfall
┌──────────────────────────────────────────────────────────────┐
│ count && <Badge /> WHEN count IS 0 │
│ │
│ count = 5: │
│ └── 5 && <Badge /> ──▶ <Badge /> (renders badge) │
│ │
│ count = 0: │
│ └── 0 && <Badge /> ──▶ 0 (renders "0") │
│ │
│ The && operator returns the left operand when it is falsy. │
│ For 0, the falsy value is 0 itself, which renders as "0". │
│ │
│ Fixes: │
│ └── {count > 0 && <Badge />} (comparison) │
│ └── {count ? <Badge /> : null} (ternary) │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Syntax | {expression} as children or attribute value |
| Valid content | Any JavaScript expression |
| Invalid content | Statements (if, for, while, declarations) |
| Conditional rendering | Ternary ? : or logical && |
| Loops | .map() and .filter().map() |
| Comments | {/* comment */} |
| Attribute literal | attr="value" (no braces) |
| Attribute expression | attr={value} (braces) |
| Object in attribute | attr={{ key: value }} (double braces) |
| Falsy values | false, null, undefined render nothing |
| Zero pitfall | 0 && ... renders “0” |
Key takeaways:
- Curly braces embed a single JavaScript expression. The expression is evaluated during render, and its result is inserted into the output.
- Only expressions are valid, not statements.
if,for,while, and declarations cannot appear inside braces because they do not produce values. - Conditionals become ternaries or logical
&&.{cond ? <A /> : <B />}handles if/else;{cond && <A />}handles if without else. - Loops become array methods.
.map()transforms an array into an array of elements;.filter().map()filters and transforms. - Attributes accept expressions with braces. String literals need no braces; variables and expressions do. Object values require double braces.
- Falsy values render nothing.
false,null, andundefinedproduce no output, which makes&&conditional rendering work. - The zero pitfall breaks
&&for numbers.0 && <Badge />renders “0” because0is falsy and is returned by&&. Use a comparison or ternary. - Helper functions hold complex logic. When branching requires statements, put them in a function and call the function in the expression position.
Remember: JSX is JavaScript with a syntax for describing elements. The curly braces are not a template language; they are the boundary between markup and expression. Anything that produces a value can go inside them: variables, arithmetic, function calls, ternaries, and array methods. Anything that performs an action — if, for, while, declarations — cannot, because it does not produce a value to insert. This constraint is what makes JSX predictable and analyzable. When the logic is too complex for a single expression, move it into a function and call the function. The result is markup that stays declarative while still expressing dynamic behavior.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!