JSX Fundamentals — Writing HTML in JavaScript

JSX (JavaScript XML) is the syntax extension that makes React components readable and writable. It lets you write HTML-like markup directly inside JavaScript functions — and Vite/Babel compiles it into plain React.createElement() calls before the browser ever sees it. JSX is not required to use React, but it is used in virtually every React codebase because it makes the structure of your UI immediately visible at a glance. Understanding JSX rules — what makes it different from HTML, how to embed JavaScript, and the common pitfalls — is a fundamental skill for every MERN developer.

JSX vs HTML — Key Differences

HTML JSX Reason
class="..." className="..." class is a reserved keyword in JavaScript
for="..." htmlFor="..." for is a reserved keyword in JavaScript
<br> <br /> All tags must be self-closed in JSX
<img src="..."> <img src="..." /> All void elements must self-close
Multiple root elements One root element (or Fragment) JSX must return a single expression
onclick="fn()" onClick={fn} camelCase event names, function reference not string
style="color: red" style={{ color: 'red' }} Inline styles are JavaScript objects, not strings
HTML comments {/* JSX comment */} HTML comments are not valid JSX
Note: JSX is syntactic sugar — it is not HTML and it is not a templating language. Every JSX element compiles to a React.createElement(type, props, ...children) call. <h1 className="title">Hello</h1> becomes React.createElement('h1', { className: 'title' }, 'Hello'). This is why JSX attributes use camelCase (JavaScript object keys) rather than HTML attribute names.
Tip: Use React Fragments (<>...</> or <React.Fragment>...</React.Fragment>) when a component needs to return multiple adjacent elements without adding an extra wrapper <div> to the DOM. Unnecessary wrapper divs can break CSS layouts (flexbox, grid children) and add clutter to the DOM tree.
Warning: Never put if/else statements or for loops directly inside JSX return values — JSX is an expression, not a statement block. Use the ternary operator for conditionals and Array.map() for loops inside JSX. For complex conditional logic, compute the result in a variable before the return statement and reference the variable in JSX.

Basic JSX Syntax

// JSX must return one root element
function Welcome() {
  return (
    <div className="welcome">    {/* className, not class */}
      <h1>Hello, MERN!</h1>
      <p>Welcome to the blog.</p>
      <hr />                      {/* self-closing void element */}
      <img src="/logo.png" alt="MERN Blog logo" />  {/* self-closing */}
    </div>
  );
}

// Multiple root elements — use a Fragment
function Actions() {
  return (
    <>                              {/* Fragment — no extra DOM element */}
      <button>Save</button>
      <button>Cancel</button>
    </>
  );
}

Embedding JavaScript Expressions

function PostCard({ title, author, viewCount, createdAt }) {
  const formattedDate = new Date(createdAt).toLocaleDateString('en-US', {
    year: 'numeric', month: 'long', day: 'numeric',
  });

  return (
    <article className="post-card">
      {/* Curly braces embed any JS expression */}
      <h2>{title}</h2>

      {/* Method calls */}
      <p>By {author.name.toUpperCase()}</p>

      {/* Variables */}
      <time dateTime={createdAt}>{formattedDate}</time>

      {/* Ternary — conditional text */}
      <span className={viewCount > 100 ? 'popular' : 'normal'}>
        {viewCount} {viewCount === 1 ? 'view' : 'views'}
      </span>

      {/* Template literals */}
      <p className={`card card--${viewCount > 1000 ? 'viral' : 'standard'}`}>
        {author.bio || 'No bio provided'}
      </p>
    </article>
  );
}

Inline Styles

// Inline styles in JSX — JavaScript object with camelCase property names
function ProgressBar({ percentage }) {
  return (
    <div
      className="progress-bar"
      style={{              // outer {} = JSX expression, inner {} = JS object
        width:           `${percentage}%`,
        backgroundColor: percentage > 80 ? '#22c55e' : '#3b82f6',
        height:          '8px',
        borderRadius:    '4px',
        transition:      'width 0.3s ease',
      }}
    />
  );
}

// CSS property names: camelCase in JSX vs kebab-case in CSS
// CSS:              background-color, font-size, border-radius
// JSX style prop:   backgroundColor,  fontSize,  borderRadius

What JSX Compiles To

// JSX (what you write)
const element = (
  <h1 className="title" id="main-heading">
    Hello, {name}!
  </h1>
);

// After Babel/Vite compilation (what the browser runs)
const element = React.createElement(
  'h1',
  { className: 'title', id: 'main-heading' },
  'Hello, ',
  name,
  '!'
);

// Both produce the same result — JSX is just friendlier syntax

Common Mistakes

Mistake 1 — Using class instead of className

❌ Wrong — using the HTML attribute name:

<div class="post-card"> {/* class is reserved in JS — will still work but triggers a warning */}

✅ Correct — use the JSX attribute name:

<div className="post-card"> {/* ✓ */}

Mistake 2 — Returning multiple root elements without a Fragment

❌ Wrong — JSX cannot return adjacent elements without a wrapper:

return (
  <h1>Title</h1>
  <p>Content</p>   // SyntaxError: Adjacent JSX elements must be wrapped
);

✅ Correct — wrap in a Fragment or a div:

return (
  <>
    <h1>Title</h1>
    <p>Content</p>
  </>
);

Mistake 3 — Using if/else inside JSX return value

❌ Wrong — if statements are not valid expressions in JSX:

return (
  <div>
    {if (isLoading) { return <Spinner /> }}  {/* SyntaxError */}
  </div>
);

✅ Correct — use ternary or logical AND inside JSX, or compute before return:

return (
  <div>
    {isLoading ? <Spinner /> : <PostList posts={posts} />}
    {error && <ErrorMessage message={error} />}
  </div>
);

Quick Reference

HTML JSX equivalent
class="..." className="..."
for="..." htmlFor="..."
<br> <br />
style="color:red" style={{ color: 'red' }}
onclick="fn()" onClick={fn}
<!-- comment --> {/* comment */}
Multiple roots <>...</> (Fragment)

🧠 Test Yourself

You write <div style="color: red; font-size: 16px"> in a JSX file. What error will you see?