JavaScript in JSX with curly braces
Use curly braces in JSX to show values, run expressions, set attributes from variables, and pass style objects, with live React examples you can edit.
- 01Show values with bracesPut any JavaScript expression between tags, and say what React shows for text, numbers, booleans, null, arrays, and objects.
- 02Tell an expression from a statementExplain why
ifandforcan't go inside braces, and use a ternary or a variable worked out before thereturninstead. - 03Set attributes and styles from dataChoose between quotes and braces for an attribute, and write a style object with double curlies, camelCase names, and numbers in px.
A card that reads its own data
In Writing markup with JSX, every tea card had its words typed straight into the tags: <h3>Green tea</h3>. A real shop keeps its teas as data: names, prices, and how many are left. To show data, JSX has curly braces, { }. Here is a card that reads three values. The result is live:
const tea = "Green tea";const price = 3;const cups = 2; export default function TeaCard() { return ( <article className="tea-card"> <h3>{tea}</h3> <p>{cups} cups cost ${price * cups}</p> </article> );}Lines 1 to 3 are ordinary JavaScript variables. Line 8 has braces around tea, so the heading shows the variable's value, Green tea, not the word "tea". Line 9 has two pairs of braces. The first shows cups, 2. The second works out price * cups, 3 times 2, and shows 6. The $ in front of it is plain text, like the word "cost". The result reads 2 cups cost $6.
Press Edit & run and change cups on line 3 to 3. The result says 3 cups cost $9: the braces are worked out again every time the component runs, from whatever the data is now.
<h3>{tea}</h3> switch from markup back to JavaScript. React reads tea when the component runs, and its value, Green tea, goes into the heading.A shop sends the same letter to every customer, with blanks for the name and the order. The template never changes. Each time, the office fills the blanks from one customer's details, and only something that fits in a blank, like a name or a number, can go there.
- In real life: A letter with blanks: "Dear ____, your order of ____ cups is ready"
- In React: JSX with braces:
<p>{cups} cups cost ${price * cups}</p> - In real life: The office fills each blank from the customer's details
- In React: React works out each pair of braces when the component runs
- In real life: Each blank takes a word or a number, never an instruction
- In React: Each pair of braces takes an expression, never a statement
- In real life: New details, new letters, same template
- In React: New data, new result, same component
Curly braces in JSX switch from markup back to JavaScript. Whatever you write inside them must be an expression, code that works out to a value. React works it out each time the component runs, and puts the value in that spot: as text between tags, or as an attribute's value.
Braces between tags show a value
Between tags, the braces can hold any expression, not only a variable: a sum, a method call, a property, an item from an array, or a call to your own function. This order summary uses each:
const customer = "Asha";const items = ["Green tea", "Lemon cake", "Iced tea"];const total = 11.5; function formatPrice(amount) { return "$" + amount.toFixed(2);} export default function OrderSummary() { return ( <section> <h3>Thanks, {customer.toUpperCase()}!</h3> <p>You ordered {items.length} items.</p> <p>First up: {items[0]}</p> <p>Total: {formatPrice(total)}</p> </section> );}- Line 12 calls a string method:
customer.toUpperCase()givesASHA, so the heading readsThanks, ASHA!. - Line 13 reads a property:
items.lengthis 3, so the paragraph readsYou ordered 3 items. - Line 14 reads the first item of the array:
items[0]isGreen tea. - Line 15 calls
formatPrice, the function on lines 5 to 7, which turns 11.5 into$11.50withtoFixed(2). Moving a long expression into a function keeps the markup easy to read.
Why does any expression work? Remember from Writing markup with JSX that every tag becomes a call to jsx(), with what's between the tags in children. Here is line 13 as TypeScript's compiler writes it:
jsxs("p", { children: ["You ordered ", items.length, " items."] });The text and the braces became one array of children: the text "You ordered ", then the expression items.length exactly as you wrote it, then " items.". The braces simply drop your JavaScript into the call. So anything you could write as a value in an array can go in the braces, and React shows the value it gets.
But React can't show every value. Pick one below, and React renders it on this page between square brackets, which are plain text so you can see where the value goes:
const value = "Green tea"; export default function Shelf() { return <p>[{value}]</p>;}renderingRendering…
Strings and numbers show. Even 0 and NaN show, because they're numbers. true, false, null, and undefined leave nothing, so the brackets stay empty: React skips them on purpose, which is what lets you write conditions in braces later. An array shows each item, one after another. A plain object stops React with an error.
0. Booleans, null, and undefined leave nothing behind. An array shows each item. A plain object stops React with an error.Here is the object case in a real component, with its error on purpose:
const tea = { name: "Green tea", price: 3 }; export default function TeaCard() { return <h3>{tea}</h3>;}Line 1 makes an object with two properties, and line 4 puts the whole object between the tags. The result shows React's error: Objects are not valid as a React child (found: object with keys {name, price}). A child is anything between a tag's opening and closing parts. React has no way to know whether you wanted the name, the price, or both. Press Edit & run and change {tea} to {tea.name}: the heading shows Green tea.
How to read this table: each row is one kind of value between the braces, what the page shows, and why.
| When the braces hold | The page shows | Why |
|---|---|---|
Text, like "Green tea" | The text. | A string is what a page is made of. |
A number, like 3, 0, or NaN | The number, even 0 and NaN. | Numbers are values worth showing, so React never skips them. |
true or false | Nothing. | React skips booleans, so a condition in braces can leave no trace. |
null or undefined | Nothing. | Both mean "no value", so there is nothing to show. |
An array, like ["Tea", "Cake"] | Each item, one after another. | React shows every item in order. Lists are built this way. |
A plain object, like { name: "Tea" } | An error, and nothing renders. | React can't guess which property you meant. Write one, like tea.name. |
Only an expression fits in the braces
JavaScript code comes in two kinds, as the Code structure lesson explains. An expression works out to a value: price * cups, tea.name, formatPrice(total). A statement does something but gives back no value: if, for, const total = 3. The braces need a value, so only an expression fits. Here is an if in the braces:
const cupsLeft = 3; export default function StockBadge() { return ( <p> {if (cupsLeft > 0) { "In stock" } else { "Sold out" }} </p> );}Nothing renders. The result shows a syntax error, Unexpected token (6:8): line 6, at the if. Picture what the compiler would have to write, with the braces dropped into the call as they are:
jsx("p", {
children: if (cupsLeft > 0) { "In stock" } else { "Sold out" },
});Line 2 puts an if where a property's value goes. That's not JavaScript, any more than const label = if (…) is. There are two ways to fix it. Inside the braces, use an expression that makes the choice, the ternary operator condition ? a : b. Or make the choice with if before the return, store the result in a variable, and show the variable:
const cupsLeft = 3; export default function StockBadge() { let note = ""; if (cupsLeft > 0 && cupsLeft < 5) { note = " Order soon!"; } return ( <p> {cupsLeft > 0 ? `${cupsLeft} cups left.` : "Sold out."} {note} </p> );}Line 11 is a ternary inside the braces: when cupsLeft > 0, it gives the template literal `${cupsLeft} cups left.`, and otherwise "Sold out.". Lines 4 to 7 are an ordinary if, above the return, where statements are fine: it sets note when only a few cups are left. Line 12 shows note. The result reads 3 cups left. Order soon!
Press Edit & run and set cupsLeft on line 1 to 0: Sold out., and the if leaves note empty. Set it to 20: 20 cups left., with no note. Loops work the same way: a for loop can't go in braces, but items.map(…) is an expression that gives back an array, which is how the Rendering lists lesson builds lists. Choosing between whole pieces of markup gets its own lesson too, Conditional rendering.
Braces as an attribute's value
Braces work in a second place: right after the = of an attribute, in place of the quotes. The attribute then gets the expression's value. This sign-up form takes four attributes from variables:
const field = "email";const hint = "you@example.com";const maxLetters = 40;const joined = false; export default function JoinForm() { return ( <form className={joined ? "join done" : "join"}> <label htmlFor={field}>Email</label> <input id={field} type="email" placeholder={hint} maxLength={maxLetters} /> <button type="button" disabled={joined}> {joined ? "You're in" : "Join"} </button> </form> );}- Line 8:
classNamegets a ternary. Whilejoinedis false, the form's class isjoin. - Lines 9 and 10:
htmlForandidboth readfield, so the label and the box always match. Click Email in the result: the cursor jumps into the box. - Line 10:
placeholdergets the string inhint, the grey text in the empty box, andmaxLengthgets the number 40, so the box takes at most 40 letters. - Line 11:
disabledgets a boolean.falseleaves the attribute off, so the button works.
Press Edit & run and set joined on line 4 to true. Three things change from one value: the class becomes join done, the button is disabled, and line 12 makes it say You're in.
Quotes and braces are easy to mix up. Here is the same name, hint, written three ways:
const hint = "you@example.com"; export default function ThreeBoxes() { return ( <> <input placeholder="hint" /> <input placeholder="{hint}" /> <input placeholder={hint} /> </> );}The first box, line 6, says hint: quotes give the attribute exactly the text inside them. The second, line 7, says {hint}, braces and all: inside quotes, braces are ordinary characters. Only the third, line 8, shows you@example.com, the variable's value. The middle one is a common slip, and nothing warns you about it.
hint.Use quotes for fixed text and braces for everything else. Braces matter most for values that aren't text. Here is false written both ways on a button:
export default function OrderButtons() { return ( <> <button disabled={false}>Order with braces</button> <button disabled="false">Order with quotes</button> </> );}Line 4 gives disabled the boolean false, so the first button works. Line 5 gives it the string "false", and a non-empty string counts as true, so the second button is disabled. Try clicking both. Press Edit & run to read React's warning in the console: Received the string `false` for the boolean attribute `disabled`. … Did you mean disabled={false}?
Braces go only in those two places: between tags, and right after an attribute's =. They can't make a tag name or an attribute name. This doesn't compile, because the compiler needs to read every tag's name as it is:
const tag = "h3";
<{tag}>Green tea</{tag}>How to read this table: each row is one way to write an attribute's value, what the attribute gets, and when to use it.
| When you write | The attribute gets | Use it for |
|---|---|---|
placeholder="hint" | The text hint. | Fixed text that never changes. |
placeholder="{hint}" | The text {hint}, braces and all. | Nothing. It's almost always a mistake. |
placeholder={hint} | The value of the variable hint. | Text that comes from your data. |
| A template literal in braces | Text built from fixed words and values. | Mixing words with data, like Search 12 teas. |
maxLength={40} | The number 40. | Numbers, so you can do sums with them first. |
disabled={joined} | The attribute when joined is true, none when false. | On and off attributes, from a condition. |
Double curlies: a style object
In HTML, style takes a string of CSS. In JSX it takes an object, with one property per CSS declaration. An object literal has braces of its own, so when you write it right in the attribute, you get two pairs:
export default function SaleBadge() { return ( <p style={{ display: "inline-block", backgroundColor: "#c2410c", color: "white", padding: 6, borderRadius: 4, }} > 20% off green teas </p> );}Line 4 opens two braces. The outer one switches to JavaScript, as for any attribute. The inner one starts an object, which closes on line 10 with }}. Lines 5 to 9 are its properties. They're named in camelCase, like the element.style properties in plain JavaScript: backgroundColor on line 6 for CSS's background-color, borderRadius on line 9 for border-radius. Line 8 gives padding a plain number, 6, and React writes 6px.
Nothing about double curlies is special. Put the object in a variable, and you're back to single braces. This badge looks exactly the same:
const badgeStyle = { display: "inline-block", backgroundColor: "#c2410c", color: "white", padding: 6, borderRadius: 4,}; export default function SaleBadge() { return <p style={badgeStyle}>20% off green teas</p>;}Lines 1 to 7 make the object, and line 10 passes it with one pair of braces: style={badgeStyle}. Both badges get the same style attribute on the page.
style={{ padding: 6 }} is one pair of braces inside another. The outer pair switches to JavaScript, the inner pair is an object, and React turns each of its properties into CSS on the element.A string of CSS, the HTML way, is an error on purpose here:
export default function SaleBadge() { return <p style="color: #c2410c; padding: 6px">20% off green teas</p>;}React stops at line 2 and says so: The `style` prop expects a mapping from style properties to values, not a string. A mapping here means an object. Turn the string into {{ color: "#c2410c", padding: 6 }} with Edit & run, and the badge text appears.
Which numbers get px? Pick a style object below, and see the style attribute React really writes:
export default function TeaName() { const style = { fontSize: 20 }; return <p style={style}>Green tea</p>;}style attributeGreen tea
…
Rendering…
Sizes like fontSize and padding get px. Properties that have no unit in CSS, like lineHeight, opacity, and zIndex, keep the bare number. For any other unit, write a string: "1.25rem".
Most styling belongs in a CSS file, with className; the Styling components lesson compares the ways to style. A style object earns its place when a value comes from your data, like how full a loyalty card is:
const stamps = 7;const needed = 10; export default function LoyaltyCard() { const percent = (stamps / needed) * 100; return ( <section> <p>{stamps} of {needed} stamps. Your next tea is free at {needed}.</p> <div className="bar"> <div className="fill" style={{ width: `${percent}%` }} /> </div> </section> );}Line 5 works out the percent: 7 of 10 is 70. Line 11 sets only the width in a style object, with a template literal that makes "70%". The bar's fixed look, its height, colors, and rounded ends, sits in the styles.css tab under the class names on lines 10 and 11. Change stamps on line 1 to 9 and the bar grows to 90%.
A product card built from one object
In a real shop, product cards come from data, often one object per product from a server. Every part of this card that can change reads the product object, using each kind of braces from this lesson:
const product = { name: "Jasmine green tea", price: 4.5, stock: 3, color: "#15803d", tags: ["green", "floral"],}; export default function ProductCard() { const lowStock = product.stock < 5; return ( <article className="product" style={{ borderLeftColor: product.color }}> <h3>{product.name}</h3> <p>${product.price.toFixed(2)} · {product.tags.join(", ")}</p> <p className={lowStock ? "stock low" : "stock"}> {lowStock ? `Only ${product.stock} left` : "In stock"} </p> <button aria-label={`Add ${product.name} to cart`} disabled={product.stock === 0}> Add to cart </button> </article> );}- Line 10 works out a value before the
return:lowStockis true when fewer than 5 are left. - Line 13 gets the border color from the data with a style object,
borderLeftColor. The rest of the card's look is in thestyles.csstab. - Line 15 shows the price with two decimals,
$4.50, and joins the tags intogreen, floral. - Lines 16 and 17 pick a class and a message with two ternaries: with 3 left, the line says
Only 3 left, in thelowstyle. - Line 19 builds the button's
aria-label, what a screen reader says, from the name, and turns the button off at 0.
Press Edit & run and set stock on line 4 to 12: the line says In stock. Set it to 0: the button turns off, but the line says Only 0 left. Exercise 4 fixes that.
price * cupsif (stock > 0) "In stock"tea.name.toUpperCase()for (const tea of teas) teastock > 0 ? "In stock" : "Sold out"const total = price * cups`Only ${stock} left`formatPrice(total)
Sort each piece of code: could it go between the braces in <p>{…}</p>?
Common mix-ups
- "
${price}in JSX is a template literal." No. Outside backticks, the$is plain text and{price}is a pair of braces. That's why${price}shows$3. - "Anything can go in the braces." No. Only an expression, code that makes a value.
if,for, andconstare statements: put them above thereturn. - "Quotes and braces do the same thing in an attribute." No.
placeholder="{hint}"shows the text{hint}. Onlyplaceholder={hint}reads the variable. - "React shows
falseandnullas words." No. It shows nothing fortrue,false,null, andundefined. But it does show0andNaN. - "Double curlies are special JSX syntax." No. They're an object literal inside ordinary braces.
- "
styletakes CSS text, like in HTML." No. It takes an object with camelCase names; a string throws an error. - "
disabled="false"turns the attribute off." No. The string"false"turns it on. Writedisabled={false}.
Practice exercises
Each exercise shows its result under the code, or when you press Edit & run. Change the code, and the result updates as you type. Try it yourself first, then use the hints.
What does the paragraph show? Answer first, then press Edit & run to check.
const tea = "Iced tea";
const price = 4;
const cups = 3;
export default function TeaCard() {
return (
<article>
<h3>{tea}</h3>
<p>{cups} cups cost ${price * cups}</p>
</article>
);
}const tea = "Iced tea";const price = 4;const cups = 3; export default function TeaCard() { return ( <article> <h3>{tea}</h3> <p>{cups} cups cost ${price * cups}</p> </article> );}{cups} shows 3, and {price * cups} works out 4 × 3 = 12. The $ and the words are plain text, so the line reads 3 cups cost $12.
Press Edit & run: the heading throws an error. Make it show the tea's name and price, like Oolong: $5.
const tea = { name: "Oolong", price: 5 };
export default function TeaTitle() {
return <h3>{tea}</h3>;
}const tea = { name: "Oolong", price: 5 }; export default function TeaTitle() { return <h3>{tea.name}: ${tea.price}</h3>;}{tea.name} shows Oolong and {tea.price} shows 5. The colon and the $ are plain text between them, so the heading reads Oolong: $5.
The box says Search {count} teas. Make it show the real number from count.
const count = 12; export default function SearchBox() { return <input type="search" placeholder="Search {count} teas" />;}const count = 12; export default function SearchBox() { return <input type="search" placeholder={`Search ${count} teas`} />;}The braces make the attribute JavaScript, and the template literal builds one string from the words and count. The box says Search 12 teas, and it would follow count if it changed.
With stock: 0, the card says Only 0 left. Make it say Sold out when nothing is left, and keep the other two messages.
const product = { name: "Jasmine green tea", price: 4.5, stock: 0, color: "#15803d", tags: ["green", "floral"],}; export default function ProductCard() { const lowStock = product.stock < 5; return ( <article className="product" style={{ borderLeftColor: product.color }}> <h3>{product.name}</h3> <p>${product.price.toFixed(2)} · {product.tags.join(", ")}</p> <p className={lowStock ? "stock low" : "stock"}> {lowStock ? `Only ${product.stock} left` : "In stock"} </p> <button aria-label={`Add ${product.name} to cart`} disabled={product.stock === 0}> Add to cart </button> </article> );}const product = { name: "Jasmine green tea", price: 4.5, stock: 0, color: "#15803d", tags: ["green", "floral"],}; export default function ProductCard() { const lowStock = product.stock < 5; return ( <article className="product" style={{ borderLeftColor: product.color }}> <h3>{product.name}</h3> <p>${product.price.toFixed(2)} · {product.tags.join(", ")}</p> <p className={lowStock ? "stock low" : "stock"}> {product.stock === 0 ? "Sold out" : lowStock ? `Only ${product.stock} left` : "In stock"} </p> <button aria-label={`Add ${product.name} to cart`} disabled={product.stock === 0}> Add to cart </button> </article> );}The new line 17 checks for 0 first, then for a low stock, then falls back to In stock. A ternary inside a ternary is fine for three choices; for more, an if before the return is easier to read.
The card has 7 of 10 stamps, but the bar's fill is short. Make the fill cover 70% of the bar.
const stamps = 7;const needed = 10; export default function LoyaltyCard() { const percent = (stamps / needed) * 100; return ( <section> <p>{stamps} of {needed} stamps. Your next tea is free at {needed}.</p> <div className="bar"> <div className="fill" style={{ width: percent }} /> </div> </section> );}const stamps = 7;const needed = 10; export default function LoyaltyCard() { const percent = (stamps / needed) * 100; return ( <section> <p>{stamps} of {needed} stamps. Your next tea is free at {needed}.</p> <div className="bar"> <div className="fill" style={{ width: `${percent}%` }} /> </div> </section> );}A plain number on a size gets px, so width: percent made the fill 70px wide, whatever the card's width. The template literal makes the string "70%", so the fill takes 70% of the bar.
The line says 1 items · $4.5. Make it say item for one and items for more, with the price in two decimals.
const count = 1;const total = 4.5; export default function ReceiptLine() { return ( <p> {count} items · ${total} </p> );}const count = 1;const total = 4.5; export default function ReceiptLine() { return ( <p> {count} {count === 1 ? "item" : "items"} · ${total.toFixed(2)} </p> );}The ternary picks the word from count, and toFixed(2) turns 4.5 into 4.50. With count at 3, the same line says 3 items.
Check your understanding
For each code question, find what's inside each pair of braces first, then work out its value. Press Edit & run on a question's code to check your answer.
Question 1 of 7What text does this order line show?
Read the code, then predictconst cups = 0; const sugar = false; export default function Order() { return <p>Cups: {cups}, sugar: {sugar}</p>; }Choose an answer to see the explanation.
Question 2 of 7Which of these can go inside JSX curly braces?
Choose an answer to see the explanation.
Question 3 of 7What does this box show as its placeholder?
Read the code, then predictconst city = "Pune"; export default function CityBox() { return <input placeholder="{city}" />; }Choose an answer to see the explanation.
Question 4 of 7Why does
style={{ color: "teal" }}have two pairs of braces?Choose an answer to see the explanation.
Question 5 of 7What does React write in this paragraph's style attribute?
Read the code, then predictexport default function Note() { return <p style={{ padding: 8, lineHeight: 1.5 }}>Steep for 3 minutes.</p>; }Choose an answer to see the explanation.
Question 6 of 7What happens with
<button disabled="false">Order</button>?Choose an answer to see the explanation.
Question 7 of 7What happens when this order line renders?
Read the code, then predictconst order = { tea: "Chai", cups: 2 }; export default function OrderLine() { return <p>{order}</p>; }Choose an answer to see the explanation.
Key takeaways
- Curly braces in JSX switch to JavaScript. React works out what's inside each time the component runs.
- Braces go in two places: between tags, to show a value, and right after an attribute's
=, to set it. - Only an expression fits, because the braces become a value in a
jsx()call. Use a ternary in the braces, or anifbefore thereturn. - Strings and numbers show, even
0.true,false,null, andundefinedshow nothing. Arrays show each item. Plain objects are an error. - Quotes give an attribute fixed text; braces give it a value.
styletakes an object, so writing it in place gives double curlies, with camelCase names andpxadded to plain numbers on sizes.
Remember the one-liner.
Braces are a window into JavaScript: one expression in, one value out, on the page.
Coming next: Passing props to a component. It uses these braces to give each component its own data, the way a menu page gives each tea card its own name and price.