Get eBook BundleVolume I index
JSX Is Real React Code

Expressions Inside JSX

Ishtmeet Singh @ishtms/July 20, 2026/15 min read
#react#jsx#javascript#expressions#rendering

JSX would be pretty boring if everything inside it had to stay fixed.

I mean, imagine writing React but every heading, username, lesson count, button label, etc. had to be hardcoded. Not very useful, right? We need some way to take values from JavaScript and put them inside the JSX we're returning.

That's what the curly braces are doing.

jsx
function LessonHeading({ lesson, total }) {
  return (
    <h1>Lesson {lesson} of {total}</h1>
  );
}

The Lesson, of, and spaces around them are normal JSX text. But once React reaches {lesson}, we're asking JavaScript to evaluate lesson and use whatever value comes back. Same for {total}.

So if lesson is 3 and total is 10, we end up seeing:

text
Lesson 3 of 10

The curly braces themselves obviously don't appear on the page. They're part of the JSX syntax. They mean, "okay, JavaScript expression starts here."

And the word expression is kinda important, because you can't just throw any random JavaScript inside those braces.

Okay, what's an expression then?

An expression is JavaScript that produces a value.

That's probably the easiest definition to work with here.

Look at these:

jsx
<p>{2 + 3}</p>
<p>{title.toUpperCase()}</p>
<p>{lesson.minutes}</p>

2 + 3 produces 5. title.toUpperCase() calls a function and produces a string. lesson.minutes reads a property and produces whatever value is stored there.

React doesn't really care how complicated the expression was. JavaScript evaluates it first, and React receives the resulting value.

So this:

jsx
<p>{2 + 3}</p>

is not React somehow learning arithmetic. JavaScript evaluates 2 + 3, gets 5, and then React gets that 5 as a child of the <p>.

You can roughly read the process as:

text
evaluate JavaScript expression
  -> get some JavaScript value
  -> give that value to React
  -> React handles it based on its type

And remember, your component can render again. Which means these expressions can run again too. If you're doing calculations inside JSX, they should normally just calculate something from the values available during that render. Don't start changing outside state, writing into storage, making requests, or doing other side effects from there.

Why can't I put an if inside the braces?

Because if is a statement.

And a statement doesn't produce a value you can place there.

This won't work:

jsx
function Status({ complete }) {
  return <p>{if (complete) "Complete"}</p>;
}

JavaScript can't even parse that code.

The braces are expecting an expression. So if you want to choose between two values right there, use something that actually produces a value.

The conditional operator works:

jsx
const label = complete ? "Complete" : "In progress";

return <p>{label}</p>;

Or just put the expression directly inside JSX:

jsx
return <p>{complete ? "Complete" : "In progress"}</p>;

You can still use a normal if, of course. Just do it before the JSX.

jsx
let label;

if (complete) {
  label = "Complete";
} else {
  label = "In progress";
}

return <p>{label}</p>;

Here the if controls which assignment runs, and by the time we reach {label}, JavaScript already has a normal string value ready for React.

Same story with loops such as for and while. Those are statements too, so you don't put a for loop directly inside a JSX child position. Run it before returning if you really need one, or quite often you'll use array methods such as map() and filter() because those calls return values.

We'll get into map() properly later because React code uses it a lot. Like, a lot.

You can use expressions inside element content

The most obvious place for curly braces is between opening and closing tags.

jsx
const completed = 2;
const total = 6;

return <p>{completed} of {total} complete</p>;

React gets the two numbers and places them along with the surrounding text.

So the visible result becomes:

text
2 of 6 complete

You do have to pay some attention to spaces when JSX and expressions are sitting next to each other.

This works fine:

jsx
<p>{total} complete</p>

because there is already a space in the JSX text before complete.

But sometimes you'll have separate elements next to each other and need an actual text space between them:

jsx
<strong>{completed}</strong>{" "}
<span>lessons complete</span>

{" "} is just a JavaScript string containing one space.

Use that when you actually need whitespace inside the sentence. If you're trying to visually separate blocks, buttons, cards, or other elements, do that with CSS. gap, margin, flexbox, grid, whatever fits the layout.

Curly braces work in props too

Curly braces aren't only for text between tags. Props can take JavaScript values as well.

Take this:

jsx
<progress max="6" />

Here max is written as a quoted JSX attribute, so the supplied value is a string.

Now compare it with:

jsx
<progress max={total} />

If total is a number, then we're supplying a number.

That difference becomes more obvious when you're passing props to your own components.

jsx
<LessonCard lesson={lesson} />

lesson might be an entire object. React is not turning it into some text first. The component receives the actual object reference.

Same with functions:

jsx
<button onClick={handleClick}>Continue</button>

handleClick is a function value.

One mistake people make early on is mixing the two syntaxes:

jsx
<progress max="{total}" />

Nope.

Because the whole value is inside quotes, that's just the literal string {total}. React isn't evaluating total there.

If you want JavaScript, remove the quotes:

jsx
<progress max={total} />

This becomes even more useful once props start carrying arrays, objects, booleans, callbacks, numbers, and other values. Strings can be written directly in quotes. JavaScript values go in braces.

What happens with strings and numbers?

These are easy.

React can render strings:

jsx
<p>{"ReactBook"}</p>

and numbers:

jsx
<p>{19.2}</p>

Both appear as text in the page.

So if you have:

jsx
const completed = 4;

return <p>{completed}</p>;

you see 4.

React 19 can also render a BigInt as text:

jsx
const exactCount = 9007199254740993n;

return <p>{exactCount}</p>;

You probably aren't going to use BigInt for normal lesson counters, and please don't start using it just because we mentioned it. It's useful when you genuinely need integer values outside JavaScript's safe integer range and the rest of your code supports BigInt too.

One annoying bit with BigInt is JSON. Regular JSON.stringify() doesn't serialize BigInt without extra handling, so if your data is moving through JSON APIs, you need to account for that.

There are a couple of weird numeric values too: NaN and Infinity.

They're still numbers.

So React can render them as text.

jsx
const average = totalMinutes / lessonCount;

return <p>{average}</p>;

If lessonCount is 0, you might end up showing Infinity or NaN, depending on the calculation.

React isn't going to look at that and decide your math has gone wrong. As far as React is concerned, you gave it a number.

So if zero is a valid case in your data, handle it yourself before rendering.

jsx
const average =
  lessonCount === 0
    ? 0
    : totalMinutes / lessonCount;

Or maybe the correct UI isn't 0 at all. Maybe you want "No lessons yet".

That's your application decision.

true, false, null, and undefined don't show text

This one feels weird the first time.

Try:

jsx
<p>{false}</p>
<p>{null}</p>
<p>{undefined}</p>

The <p> elements can still exist, but those values don't produce visible text inside them.

Same with true.

This behavior is also why code such as this works:

jsx
{isNew && <span>New</span>}

If isNew is true, JavaScript evaluates the && expression and gives React the <span>.

If isNew is false, the expression produces false, and React renders no visible child for it.

Pretty convenient.

But there's a small trap here.

Zero is not treated the same way.

jsx
{count && <p>You have lessons</p>}

If count is 0, JavaScript produces 0, not false.

And React renders numbers.

So you can end up with a random 0 sitting in the UI.

If you actually mean "render this only when count is above zero", then say that:

jsx
{count > 0 && <p>You have lessons</p>}

Now the left side produces a proper boolean.

And if you really want to display a boolean as text, convert it into some text yourself:

jsx
<p>{complete ? "true" : "false"}</p>

Though for an actual user interface, "Complete" and "In progress" are probably nicer than dumping true and false onto the screen.

Can I render an object?

Not directly.

Suppose we have:

jsx
const lesson = {
  title: "Expressions Inside JSX",
};

return <p>{lesson}</p>;

React doesn't know what visible child you're asking for here.

Should it print the title? The entire object? JSON? Some property you forgot to mention?

So it rejects a plain object used as a child.

What you usually wanted was one property from it:

jsx
return <p>{lesson.title}</p>;

Or if you're debugging and want to quickly inspect the object:

jsx
return <pre>{JSON.stringify(lesson, null, 2)}</pre>;

Now JSON.stringify() produces a string, and React knows how to render strings.

I wouldn't dump that into actual product UI though. It's useful while debugging, but users generally don't need to see your raw internal object with field names and all that.

Dates can surprise you for the same reason.

jsx
const publishedAt = new Date();

return <p>{publishedAt}</p>;

A Date is an object. Convert it to a string first.

jsx
const label = publishedAt.toLocaleDateString();

return <p>{label}</p>;

Now React receives a string and everything is fine.

This works:

jsx
<LessonCard lesson={lesson} />

But this doesn't:

jsx
<p>{lesson}</p>

Two totally different uses of the same JavaScript value.

Functions can go in props, but they don't become visible text

You've already seen this:

jsx
<button onClick={handleClick}>Continue</button>

We're passing handleClick as a function value.

React DOM knows that onClick is an event prop, so it can use that function when the relevant interaction happens.

But now suppose you do this:

jsx
<p>{formatTitle}</p>

Putting the function inside JSX does not call it.

You're giving React the function itself.

If you wanted whatever the function returns, then call the function:

jsx
<p>{formatTitle(title)}</p>

Now JavaScript executes formatTitle(title) during rendering, gets the return value, and React receives that result.

So if formatTitle() returns a string, good.

If it returns a React element, also fine.

If it returns some plain object you can't render, then we're back to the object problem.

Also, because the call happens while the component is rendering, formatTitle() should behave as a normal calculation. Same inputs, same kind of result, no random outside writes happening because React happened to call your component again.

JavaScript can fail before React gets anything

Sometimes people see an error coming from JSX and assume React rejected the value.

But JavaScript may have thrown before React ever received one.

Take this:

jsx
function AuthorName({ lesson }) {
  return <p>{lesson.author.name}</p>;
}

Works fine if lesson.author exists.

But if lesson.author is undefined, JavaScript tries to read .name from undefined and throws.

React never got a child value from that expression because the expression didn't finish.

If missing author data is a valid situation in your app, you can handle it:

jsx
const authorName = lesson.author?.name ?? "Unknown author";

return <p>{authorName}</p>;

?. stops the property access when author is null or undefined, and then ?? gives us the fallback string.

One small warning though: don't start putting ?. after every property in your entire app because you're scared something may be missing.

Sometimes data is required.

If every lesson must have an author, then silently showing nothing or "Unknown author" can hide a real data bug. Validate required data when it enters your application and use optional access for values that are actually allowed to be absent.

What if my string contains HTML?

Suppose some comment contains this:

jsx
const comment = "<strong>Read this</strong>";

return <p>{comment}</p>;

React DOM renders that as text.

So the user sees:

text
<strong>Read this</strong>

The browser does not create a <strong> element from that string.

This is normal React behavior. Text values passed through JSX are escaped instead of being treated as HTML markup, which also prevents normal user-provided strings from suddenly creating HTML elements or executable markup.

If you really do have HTML text that you intentionally want inserted as HTML, React has this API:

jsx
<div dangerouslySetInnerHTML={{ __html: trustedHtml }} />

Yes, the name is trying pretty hard to warn you.

Using this skips React's normal text escaping for that HTML. If the string contains unsafe markup from users, APIs, CMS content, or anywhere else you don't fully control, you can create an XSS problem.

For normal text, just render the string through braces and let React escape it.

Comments inside JSX are a bit weird

You might try writing this:

jsx
<main>
  // chapter heading
  <ChapterHeading />
</main>

But you're currently inside JSX content, not a normal JavaScript block.

Those slash characters can become part of the JSX text instead of behaving the way a JavaScript line comment would.

The usual JSX comment syntax is:

jsx
<main>
  {/* chapter heading */}
  <ChapterHeading />
</main>

Because the braces enter JavaScript and then the block comment is valid there.

Personally I'd still avoid filling JSX with comments explaining obvious tags.

This:

jsx
<main>
  {/* chapter heading */}
  <ChapterHeading />
</main>

is not telling us much. I can already see it's a chapter heading.

If some non-obvious logic needs explaining, I'd rather keep that explanation beside the JavaScript doing the calculation:

jsx
const heading = buildChapterHeading(chapter);

return (
  <main>
    <ChapterHeading title={heading} />
  </main>
);

Then the JSX stays fairly easy to scan.

Don't make JSX expressions unnecessarily huge

Technically you can put a lot inside one pair of braces.

For example:

jsx
<p>
  {lessons
    .filter((lesson) => lesson.published)
    .map((lesson) => lesson.title)
    .join(", ")}
</p>

Nothing wrong with JavaScript there. filter() returns an array, map() returns another array, join() returns a string, and React eventually gets that string.

But once an expression starts doing multiple bits of work, I usually prefer moving it above the JSX.

jsx
const publishedLessons = lessons.filter(
  (lesson) => lesson.published
);

const titles = publishedLessons.map(
  (lesson) => lesson.title
);

const label = titles.join(", ");

return <p>{label}</p>;

Now I can inspect publishedLessons, inspect titles, put a breakpoint there, handle the empty case, reuse one of those values, etc.

You don't have to move every tiny expression out.

This is completely fine:

jsx
<p>{completed + 1}</p>

And this is fine too:

jsx
<p>{user.name.toUpperCase()}</p>

Just don't turn one pair of braces into fifteen operations and then make yourself decode it again three weeks later.

Let's build one complete calculation

Say our component receives completed lessons and total lessons:

jsx
function LessonProgress({ completed, total }) {
  const percentage = Math.round((completed / total) * 100);
  const label = `${completed} of ${total} complete`;

  return (
    <section>
      <p>{label}</p>
      <progress value={completed} max={total} />
      <p>{percentage}%</p>
    </section>
  );
}

There are a few expressions happening here, but none of them are doing anything weird.

label is a string, so React renders it as text.

completed and total are being passed as prop values to <progress>.

percentage is a number, so React renders that number as text beside the % already written in JSX.

And notice we didn't need state for any of these derived values. If completed and total already come from props, then percentage and label can just be calculated during the render.

There is one bug though.

What happens if total is 0?

Then this:

jsx
(completed / total) * 100

can produce NaN or Infinity.

So handle the case:

jsx
const percentage =
  total === 0
    ? 0
    : Math.round((completed / total) * 100);

Now the JSX always receives a finite number.

Maybe your actual product should show "No lessons yet" instead of 0%. That's fine too. JSX doesn't decide that for you. It just receives the value your JavaScript expression produced.

The two questions I'd ask whenever JSX gets confusing

When you see something inside braces and you're not sure what React is going to do with it, don't stare at the JSX syntax for five minutes.

First ask: what JavaScript value does this expression actually produce?

A string? Number? false? undefined? React element? Object? Function?

Then ask: what does React do with that type in this position?

Strings and normal numeric values can become visible text. true, false, null, and undefined don't give you visible child text. React elements describe more UI. Plain objects cannot be rendered directly as children. Functions normally belong in callback props, or you call them and render the returned result.

Once you start looking at JSX this way, curly braces stop feeling like some special React mini-language.

They're just the places where JSX lets JavaScript calculate a value.

And React gets whatever came out.