JSX & Rendering

Difficulty: Beginner

Question

What is JSX? How does it get transformed and how does React rendering work?

Answer

JSX is one of those things that seems simple on the surface - you're just writing HTML in JavaScript, right? - but understanding what's actually happening underneath gives you a much stronger foundation for debugging, performance tuning, and writing clean React code.

So let's start with what JSX actually is. JSX stands for JavaScript XML, and it's a syntax extension for JavaScript. It's not HTML, it's not a template language, and it's definitely not valid JavaScript on its own. Your browser has absolutely no idea what to do with <div className="hello">Hi</div> sitting inside a JavaScript file. JSX needs to be transformed - compiled - into regular JavaScript function calls before it can run.

This transformation is done by tools like Babel or SWC (which is what Vite uses under the hood). When you write <div className="box">Hello</div>, the compiler turns it into React.createElement('div', { className: 'box' }, 'Hello'). That's it. Every JSX tag becomes a function call. Nested elements become nested function calls. The first argument is the element type (a string for HTML elements, a reference for components), the second is an object of props, and everything after that is children.

The return value of React.createElement is just a plain JavaScript object - a React element - that looks something like { type: 'div', props: { className: 'box', children: 'Hello' } }. It's a lightweight description of what should appear on screen. It's not a DOM node. It's not rendered yet. It's just a recipe.

Starting with React 17, there's a new "automatic" JSX transform that changes how this compilation works. Instead of calling React.createElement (which required you to import React at the top of every file that used JSX), the compiler now imports special functions from 'react/jsx-runtime' automatically. The end result is the same, but you no longer need that import React from 'react' line in every file. It's a nice quality-of-life improvement, and if you see a React project where component files don't import React, now you know why.

Now let's talk about rendering - what happens after JSX is compiled.

Rendering in React is a two-phase process. Phase one is the render phase: React calls your component functions, executes all the JSX, and builds a tree of React elements - the virtual DOM. This phase is pure. It should have no side effects. React might even call your components multiple times (in Strict Mode it does this deliberately) or throw away the results if a higher-priority update comes in. Phase two is the commit phase: React takes the computed differences between the new virtual DOM tree and the previous one, and applies those changes to the actual browser DOM. This is where side effects happen - DOM mutations, refs getting attached, useLayoutEffect running.

There are some important JSX rules you need to know:

First, every JSX expression must return a single root element. You can't return two sibling elements without wrapping them. This is because JSX compiles to function calls, and a function can only return one value. The solution is Fragments - either <React.Fragment> or the shorthand <> </>. Fragments let you group elements without adding an extra DOM node. The long-form <Fragment key={...}> syntax is needed when you're rendering a list and need to attach keys.

Second, JSX uses camelCase for attributes, not the HTML attribute names. It's className instead of class (because class is a reserved word in JavaScript), htmlFor instead of for, onClick instead of onclick, tabIndex instead of tabindex. This is because JSX maps to JavaScript DOM properties, not HTML attributes.

Third, all tags must be explicitly closed. In HTML, you can write <img> or <br> without closing them. In JSX, you must write <img /> and <br />.

Fourth, you embed JavaScript expressions inside curly braces {}. Anything that evaluates to a value works: variables, function calls, ternaries, arithmetic. But you cannot put statements inside curly braces - no if/else blocks, no for loops, no variable declarations.

For conditional rendering, you have a few patterns. The logical AND operator ({condition && <Component />}) renders the component only when the condition is truthy. But watch out for a very common gotcha: the number 0 is falsy in JavaScript, but JSX renders it as the text "0" on screen. So {items.length && <List />} will show "0" when the array is empty, not nothing. Always use an explicit boolean comparison: {items.length > 0 && <List />}. For if-else scenarios, use the ternary operator: {isLoggedIn ? <Dashboard /> : <LoginPage />}.

One more thing worth knowing: JSX automatically escapes embedded values to prevent cross-site scripting (XSS) attacks. If a user types <script>alert('hacked')</script> into a form and you render {userInput}, React will display the literal text, not execute the script. This is a significant security benefit. If you genuinely need to render raw HTML (which you should rarely do), you have to use the explicitly-named dangerouslySetInnerHTML prop - the name is intentionally scary to make you think twice.

Finally, returning null from a component tells React to render nothing. This is the idiomatic way to conditionally hide a component entirely. However, returning undefined can cause issues in some configurations, so always use null for intentional "render nothing" cases.

Code examples

JSX Transformation

// What you write (JSX)
function Greeting({ name }) {
  return (
    <div className="greeting">
      <h1>Hello, {name}!</h1>
      <p>Welcome to React</p>
    </div>
  );
}

// What Babel compiles it to (classic transform)
function Greeting({ name }) {
  return React.createElement(
    'div',
    { className: 'greeting' },
    React.createElement('h1', null, 'Hello, ', name, '!'),
    React.createElement('p', null, 'Welcome to React')
  );
}

// React 17+ automatic transform (no import needed)
import { jsx as _jsx } from 'react/jsx-runtime';
function Greeting({ name }) {
  return _jsx('div', {
    className: 'greeting',
    children: [_jsx('h1', { children: ['Hello, ', name, '!'] })]
  });
}

JSX is syntactic sugar. The new automatic JSX transform in React 17+ means you no longer need to import React at the top of every file that uses JSX.

JSX Expressions and Conditionals

function Dashboard({ user, notifications }) {
  return (
    <div>
      {/* Embedding expressions */}
      <h1>Welcome, {user.name.toUpperCase()}</h1>
      <p>You have {notifications.length} notifications</p>

      {/* Conditional rendering with && */}
      {notifications.length > 0 && (
        <ul>
          {notifications.map(n => (
            <li key={n.id}>{n.message}</li>
          ))}
        </ul>
      )}

      {/* Ternary for if-else */}
      {user.isAdmin ? (
        <AdminPanel />
      ) : (
        <UserPanel />
      )}

      {/* GOTCHA: 0 is falsy but renders as "0" */}
      {notifications.length && <Badge />}  {/* Shows "0" when empty! */}
      {notifications.length > 0 && <Badge />}  {/* Correct */}
    </div>
  );
}

JSX curly braces accept any JavaScript expression. Watch out for falsy values like 0 and empty strings - they render as text instead of being hidden.

Fragments and Multiple Elements

import { Fragment } from 'react';

// Problem: JSX must return one root element
// This is INVALID:
// return (
//   <h1>Title</h1>
//   <p>Content</p>
// );

// Solution 1: Fragment long syntax
function PageHeader() {
  return (
    <Fragment>
      <h1>Title</h1>
      <p>Content</p>
    </Fragment>
  );
}

// Solution 2: Fragment shorthand
function PageHeader() {
  return (
    <>
      <h1>Title</h1>
      <p>Content</p>
    </>
  );
}

// Solution 3: Fragment with key (for lists)
function Glossary({ items }) {
  return (
    <dl>
      {items.map(item => (
        <Fragment key={item.id}>
          <dt>{item.term}</dt>
          <dd>{item.definition}</dd>
        </Fragment>
      ))}
    </dl>
  );
}

Fragments let you group elements without adding extra DOM nodes. Use the long syntax <Fragment key={...}> when you need keys in a list.

Key points

Concepts covered

JSX, createElement, Rendering, Expressions