ReactJSX: Writing Markup in JavaScript

JSX: Writing Markup in JavaScript

JSX is a syntax extension to JavaScript that lets you write HTML-like markup directly inside a JavaScript file. It is the single most recognisable feature of React — and one of the most misunderstood. JSX is not a string, not a template language, and not actual HTML. It is syntactic sugar that gets compiled into plain JavaScript function calls before the browser ever sees it.

What JSX Actually Is

Before JSX existed, building React UIs meant calling React.createElement() by hand for every element. That worked but was tedious and hard to read:

JSX
// Without JSX — pure React.createElement calls
const element = React.createElement(
  'div',
  { className: 'card' },
  React.createElement('h2', null, 'Hello, world!'),
  React.createElement('p', null, 'Welcome to React.')
)

JSX is a shorthand that compiles to exactly the same code. Here is the identical UI written with JSX:

JSX
// With JSX — compiled to the createElement calls above
const element = (
  <div className="card">
    <h2>Hello, world!</h2>
    <p>Welcome to React.</p>
  </div>
)

The Babel compiler (or the TypeScript compiler with jsx: "react-jsx") transforms every JSX element into a React.createElement() call at build time. No JSX ever reaches the browser — only the compiled JavaScript does.

Note
Since React 17, the compiler transforms JSX into calls to the new `jsx()` function from `react/jsx-runtime` rather than `React.createElement`. This means you no longer need to `import React from 'react'` at the top of every file. Older codebases still use the classic transform.
The Compilation Step in Detail

To build an intuition for what JSX compiles to, it helps to see the one-to-one mapping between JSX syntax and the function call arguments:

JSX
// JSX input
<Button type="submit" disabled={false}>
  Save changes
</Button>

// What the compiler produces (new JSX transform)
import { jsx as _jsx } from 'react/jsx-runtime'
_jsx(Button, {
  type: 'submit',
  disabled: false,
  children: 'Save changes',
})

The three arguments map to: the type (string for HTML elements, function/class for components), the props object (including children), and — in the classic transform — optional child arguments. Everything you put in JSX, the compiler faithfully converts into a plain object tree.

Why JSX Exists

React's core insight is that rendering logic and markup are inherently coupled. In traditional web development they were separated — HTML in .html files, JavaScript in .js files — but in practice a button's rendering and its click handler belong together because they change together. JSX makes that co-location natural:

JSX
function SubmitButton({ isLoading, onSubmit }) {
  return (
    <button
      type="submit"
      onClick={onSubmit}
      disabled={isLoading}
      className={isLoading ? 'btn btn--loading' : 'btn'}
    >
      {isLoading ? 'Saving…' : 'Save changes'}
    </button>
  )
}

The markup, the conditional class, the event handler, and the loading text are all in one place. Changing the button requires touching one file, not three.

JSX is Not HTML

JSX looks like HTML but has important differences. The most common surprises for new React developers are:

HTML

JSX

Reason

class="container"

className="container"

class is a reserved JS keyword

for="email"

htmlFor="email"

for is a reserved JS keyword

<br>

<br />

All JSX tags must be closed

<img src="...">

<img src="..." />

Self-closing tags require the slash

onclick="handler()"

onClick={handler}

Events are camelCase props, not strings

style="color:red"

style={{ color: "red" }}

Style is a JS object, not a string

<!-- comment -->

{/* comment */}

HTML comments are invalid in JSX

JSX Must Return One Root Element

Because JSX compiles to a function call that returns a single value, every JSX expression must have exactly one root element. Returning two sibling elements without a wrapper is a syntax error:

JSX
// ❌ Error: Adjacent JSX elements must be wrapped
function Broken() {
  return (
    <h1>Title</h1>
    <p>Paragraph</p>
  )
}

// ✅ Wrap in a fragment (no extra DOM node)
function Fixed() {
  return (
    <>
      <h1>Title</h1>
      <p>Paragraph</p>
    </>
  )
}
JSX Expressions vs Statements
Inside JSX, curly braces `` let you embed any JavaScript **expression** — a piece of code that evaluates to a value. But you cannot embed **statements** like `if`, `for`, or `while` directly:

JSX
// ✅ Expressions are fine
const name = 'Alice'
const element = <h1>Hello, {name}!</h1>

// ✅ Function calls (which return a value) work too
const element = <p>{new Date().toLocaleDateString()}</p>

// ❌ Statements cannot appear inside {}
const element = (
  <div>
    {if (isLoggedIn) { return <UserPanel /> }} // SyntaxError
  </div>
)
Tip
Use a ternary expression (`condition ? a : b`) or the logical AND operator (`condition && element`) for conditional rendering inside JSX. For complex conditions, extract a helper function or compute the value before the return statement.
JSX is Just JavaScript

Because JSX compiles to a value (a plain object), you can store it in a variable, put it in an array, pass it as a function argument, or return it from a function — just like any other JavaScript value:

JSX
// Store JSX in a variable
const greeting = <span>Good morning!</span>

// Build an array of elements
const items = ['Apple', 'Banana', 'Cherry'].map((fruit) => (
  <li key={fruit}>{fruit}</li>
))

// Conditionally assign JSX
let icon
if (status === 'success') {
  icon = <CheckIcon />
} else if (status === 'error') {
  icon = <ErrorIcon />
}

// Use them in the render
function StatusBar({ status }) {
  return (
    <div className="status-bar">
      {icon}
      <span>{greeting}</span>
    </div>
  )
}
Warning
JSX is not sanitised HTML. React does escape string values rendered inside JSX to prevent XSS — for example, `{'<script>alert(1)</script>'}` renders as literal text. However, `dangerouslySetInnerHTML` bypasses this. Only use `dangerouslySetInnerHTML` with content you fully control and trust.
Quick Reference
  • JSX is a syntax extension — it compiles to React.createElement() (or the new jsx() function) at build time

  • JSX produces a plain JavaScript object (a React element), not real DOM

  • Every JSX expression must have one root element — use a Fragment <></> to avoid extra DOM nodes

  • JSX uses camelCase for attributes: className, onClick, htmlFor

  • Embed JavaScript expressions with curly braces {}; statements are not allowed inline

  • All tags must be closed: <br />, <img />, <MyComponent />