EduFlow
React Fundamentals
0%

Section 1 · Getting Started

JSX Rules of the Road

17 min3 min readCode playground

Lesson at a glance

Read and write JSX without fighting the compiler by knowing what it compiles to, how curly braces work, and which attribute and rendering rules differ from HTML.

  1. 1JSX is function calls
  2. 2Curly braces hold expressions
  3. 3Attributes are JavaScript names
  4. 4Close every tag
  5. 5What can be rendered

No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.

JSX looks like HTML, so your first instinct is to write HTML. Most of the time that works. Then you write class="card" and get a console warning, or put an if inside curly braces and get a compile error that points at the wrong line. Every one of these surprises makes sense once you see what JSX really is.

JSX is function calls

Vite compiles each JSX tag into a function call. This component:

JSX
function MovieCard() {
  return <h2 className="title">Arrival</h2>;
}

compiles to roughly this:

JavaScript
import { jsx } from 'react/jsx-runtime';

function MovieCard() {
  return jsx('h2', { className: 'title', children: 'Arrival' });
}

The call returns a plain JavaScript object, a React element: a lightweight description of what should be on screen, with a type and some props. You can model the idea with a tiny function of your own:

JavaScript
function h(type, props, ...children) {
  return { type, props: { ...props, children } };
}

const card = h('article', { className: 'card' },
  h('h2', null, 'Arrival'),
  h('p', null, 2016),
);

console.log(card.type);                         // article
console.log(card.props.className);              // card
console.log(card.props.children[0].props.children); // ["Arrival"]

React’s real elements carry a little more information, but the shape is the same. React walks this tree of objects and creates or updates the DOM to match.

Notice that the compiled code imports jsx for you. That’s why modern React files don’t start with import React from 'react'. Older tutorials include that line because the previous compiler turned JSX into React.createElement(…) calls, which needed React in scope. Since React 17’s automatic JSX runtime it’s unnecessary, so leave it out.

JSX source is compiled into a jsx function call, which returns a plain object with type and props; React then renders that object into a real DOM node. you write <h2>Arrival</h2> compiler makes { type: 'h2', props: {…} } React commits DOM <h2> node React elements are descriptions, not DOM nodes
JSX becomes an element object, then DOM

Once you know JSX is function calls, the rules below stop feeling arbitrary.

Curly braces hold expressions

Inside JSX, { } switches back to JavaScript. Whatever goes inside becomes an argument to a function call, so it must be an expression: something that produces a value.

JSX
function MovieCard() {
  const movie = { title: 'Arrival', year: 2016, rating: 8 };
  const age = 2026 - movie.year;

  return (
    <article>
      <h2>{movie.title.toUpperCase()}</h2>
      <p>{movie.year} · {age} years old</p>
      <p>{movie.rating >= 8 ? 'Must watch' : 'Maybe'}</p>
    </article>
  );
}

Variables, property access, method calls, arithmetic and the ternary operator a ? b : c are all expressions. if, for and const are statements, so they can’t go inside braces, in the same way you can’t pass if (x) {} as a function argument. When the logic gets bigger, compute a variable above the return and use that.

Here’s the same card with a label that needs real branching. The if lives in plain JavaScript above the return, and JSX only receives the finished value:

JSX
function MovieCard({ movie }) {
  let label;
  if (movie.rating >= 9) {
    label = 'Masterpiece';
  } else if (movie.rating >= 7) {
    label = 'Must watch';
  } else {
    label = 'Maybe';
  }

  return <p>{movie.title}: {label}</p>;
}

This pattern scales. Your JSX stays a readable picture of the output, and the decisions sit in ordinary code you can read, test and debug like any other function. (The { movie } in the parameter list is a prop; you’ll learn props properly in the next section.)

Attributes take braces too: <img src={movie.poster} alt={movie.title} />. Don’t wrap braces in quotes; src="{poster}" is the literal string {poster}.

Attributes are JavaScript names

Because props become keys of a JavaScript object, they follow JavaScript naming:

  • class → className, for → htmlFor (both are reserved words in JS).
  • Multi-word attributes are camelCase: onClick, tabIndex, autoComplete, maxLength.
  • aria-* and data-* attributes keep their dashes: aria-label="Remove", data-id="42".
  • style takes an object, with camelCase CSS properties: style={{ marginTop: 8 }}. A bare number means pixels for most properties. The double braces are an object literal inside a JSX expression.

Close every tag

JSX is stricter than HTML. Every element closes: <img />, <input />, <br />. A component returns one root, wrapped in a fragment <>…</> if you need siblings. Comments go inside braces: {/* like this */}.

What can be rendered

You put in braces What appears
A string or number The text (0 included)
A React element That element
An array of elements Each element in order
null, undefined, true, false Nothing
A plain object Error: “Objects are not valid as a React child”

The “nothing” row is what makes conditional rendering work, and the 0 in the first row is a classic trap you’ll meet in the Conditional Rendering lesson.

You can now read any JSX and predict what it does. In the next section you’ll stop hard-coding “Arrival” and learn to think in components, splitting a whole screen into pieces that receive their data through props.

Key takeaways

  • JSX compiles to function calls that create plain JavaScript objects describing the UI; React turns those objects into DOM.
  • Curly braces embed any JavaScript expression (a value), but never a statement such as if or for.
  • Attributes are camelCase JavaScript names, so you write className, htmlFor, onClick and pass style an object.
  • Strings and numbers render as text, while null, undefined, true and false render nothing and plain objects throw.

Further reading

Check your understanding

4 questions

Question 1 of 4

What does <h2 className="title">{movie.title}</h2> become after Vite compiles it?

Question 2 of 4

Which line is valid JSX?

Question 3 of 4

movie is { title: 'Arrival', year: 2016 }. What happens with <p>{movie}</p>?

Question 4 of 4

A movie’s description from your API contains <b>Must watch</b>. You render <p>{movie.description}</p>. What appears?

Practice

Code playground

Compile JSX by hand

JSX is function calls. The starter gives you h(type, props, ...children), a simplified version of what the compiler produces. Complete buildCard(movie) so it returns exactly what this JSX would create:

JSX
<article className="card">
  <h2>{movie.title}</h2>
  <p>{movie.year}</p>
</article>

Use the movie argument, not hard-coded values.

// A tiny stand-in for what JSX compiles to.
function h(type, props, ...children) {
  return { type, props: { ...props, children } };
}

// Write the h() calls this JSX compiles to:
//
// <article className="card">
//   <h2>{movie.title}</h2>
//   <p>{movie.year}</p>
// </article>
function buildCard(movie) {
  return h('article', null);
}
Loading the editor…

Ctrl/⌘ + Enter to run · Runs in a sandbox in your browser. Nothing leaves your device.

Console

(no output)

Checks

  • The root is an “article” with className “card”
  • It has two children: an “h2” then a “p”
  • The h2 contains the movie title
  • It works for any movie, not only Arrival