Chapter 4: JSX: When HTML and JavaScript Had a Baby Nobody Asked For

JSX is React’s most visible feature and its most controversial. It’s the thing that makes React developers say “it’s just JavaScript!” and everyone else say “that’s definitely not JavaScript.”

JSX is what happens when you can’t decide if you’re writing HTML or JavaScript, so you write both, poorly, at the same time.

XML in JavaScript: What Could Go Wrong?

Let’s start with a simple question: What is JSX?

According to React, JSX is “a syntax extension to JavaScript.” This is like saying a platypus is “a mammal extension to ducks.” Technically true, deeply disturbing.

Here’s JSX in all its glory:

const element = (
  <div className="container">
    <h1>Hello, {user.name}!</h1>
    <p>You have {unreadCount} unread messages.</p>
    {unreadCount > 0 && <button onClick={handleClick}>Read Messages</button>}
  </div>
);

“It looks just like HTML!” React developers exclaim.

Except:

  • class is now className
  • for is now htmlFor
  • You can’t use style as a string
  • Self-closing tags are mandatory for elements without children
  • JavaScript expressions go in curly braces
  • Conditional rendering uses JavaScript operators
  • It’s actually function calls in disguise

So it’s exactly like HTML, except for all the ways it’s different.

The Transpilation Tax

Here’s the dirty secret about JSX: browsers can’t understand it. At all. Your beautiful JSX needs to be transformed into regular JavaScript before it can run.

What you write:

<div className="hello">
  <h1>Hello, {name}</h1>
</div>

What actually runs:

React.createElement(
  'div',
  { className: 'hello' },
  React.createElement(
    'h1',
    null,
    'Hello, ',
    name
  )
)

Every. Single. Element. Becomes. A. Function. Call.

This means you need Babel or another transpiler. Which means you need a build step. Which means you need a build configuration. Which means you need webpack or Vite or whatever the build tool du jour is.

All so you can write HTML-ish syntax in JavaScript instead of just writing HTML.

Why className and htmlFor Exist

My favorite part of JSX is the inconsistency. React couldn’t use class because it’s a reserved word in JavaScript. Fair enough. But then why does this work?

<div data-class="something">  // Works fine
<div aria-label="something">  // Also fine
<div className="something">   // Required for CSS classes

The same goes for for:

<label htmlFor="input-id">    // Can't use 'for'
<label data-for="input-id">   // But this is fine

It’s almost like they could have figured out a way to make class and for work, but chose not to. Because suffering builds character.

Conditional Rendering: Ternary Operators Everywhere

Want to conditionally show something in HTML? Simple:

<!-- Server-side templating -->
<?php if ($isLoggedIn): ?>
  <p>Welcome back!</p>
<?php endif; ?>

Want to do it in JSX? Hope you like ternary operators:

{isLoggedIn ? <p>Welcome back!</p> : null}

// Or the && operator hack
{isLoggedIn && <p>Welcome back!</p>}

// Or for multiple conditions
{isLoggedIn ? (
  <p>Welcome back!</p>
) : isGuest ? (
  <p>Welcome, guest!</p>
) : (
  <p>Please log in</p>
)}

That last one is what we in the business call “ternary hell.” It’s like regular hell, but with more question marks.

The Style Attribute Disaster

In HTML, styles are strings:

<div style="color: red; font-size: 16px;">Hello</div>

In JSX, styles are objects with camelCased properties:

<div style=>Hello</div>

Notice the double curly braces? That’s not a typo. The outer braces say “here comes JavaScript” and the inner braces are the object literal. It’s braces all the way down.

Also, notice how font-size became fontSize? That’s because JavaScript doesn’t like hyphens in property names. So every CSS property you know needs to be translated to camelCase.

background-colorbackgroundColor margin-topmarginTop border-bottom-left-radiusborderBottomLeftRadius

It’s like React took CSS and said, “This isn’t confusing enough. Let’s make developers translate every property name in their heads.”

Comments: The Final Insult

Want to add a comment in HTML?

<!-- This is a comment -->

Want to add a comment in JSX?

{/* This is a comment */}

Because of course comments need to be wrapped in JSX expression syntax and use JavaScript comment syntax. It’s comments with extra steps!

But wait, it gets worse. This doesn’t work:

<div>
  // This comment breaks everything
  <p>Hello</p>
</div>

Neither does this:

<div>
  <!-- This also breaks -->
  <p>Hello</p>
</div>

You must use the special JSX comment syntax, or your app won’t compile. Because nothing says “developer friendly” like making comments complicated.

Arrays and Keys: React’s Obsession

When rendering lists in JSX, every element needs a unique key prop:

{items.map(item => (
  <li key={item.id}>{item.name}</li>
))}

Forget the key? React will yell at you in the console. Use the array index as a key? React will passive-aggressively warn you that it’s not recommended.

“But keys help React identify which items have changed!” React developers explain.

You know what else identifies which items have changed? The fact that they changed. If I update item 3, item 3 changed. This isn’t quantum physics.

The Fragment Nonsense

JSX components must return a single element. Want to return two elements? Too bad. Wrap them in a meaningless div:

// This doesn't work
return (
  <h1>Title</h1>
  <p>Paragraph</p>
);

// This does
return (
  <div>
    <h1>Title</h1>
    <p>Paragraph</p>
  </div>
);

“But that adds unnecessary DOM nodes!” you cry. React hears you and offers Fragments:

return (
  <React.Fragment>
    <h1>Title</h1>
    <p>Paragraph</p>
  </React.Fragment>
);

// Or the shorthand that looks like broken HTML
return (
  <>
    <h1>Title</h1>
    <p>Paragraph</p>
  </>
);

Yes, <> and </> are valid JSX. It’s an empty tag that doesn’t exist but also does. Schrödinger’s HTML element.

Whitespace Handling: The Invisible Enemy

JSX handles whitespace differently than HTML:

// This renders as "HelloWorld" (no space)
<div>
  Hello
  World
</div>

// This renders as "Hello World" (with space)
<div>
  Hello{' '}
  World
</div>

That’s right, you sometimes need {' '} to add a space. A space! The thing you get by pressing the largest key on your keyboard now requires special syntax.

Event Handlers: Almost Normal, But Not

HTML event handlers:

<button onclick="handleClick()">Click me</button>

JSX event handlers:

<button onClick={handleClick}>Click me</button>

Notice the differences:

  • onclickonClick (camelCase)
  • String → JavaScript expression
  • Function call → Function reference

Pass the wrong thing and enjoy your errors:

<button onClick={handleClick()}>Click me</button>
// Executes immediately, not on click

<button onClick="handleClick">Click me</button>
// String is not a function error

The Boolean Attribute Trap

In HTML, boolean attributes work like this:

<input disabled>
<input disabled="disabled">
<input disabled="">
<!-- All three are disabled -->

In JSX:

<input disabled={true} />   // Disabled
<input disabled={false} />  // Not disabled
<input disabled />          // Disabled (shorthand for true)
<input disabled="" />       // Disabled (empty string is truthy for attributes)
<input disabled={0} />      // Not disabled (0 is falsy)

The rules are:

  • true means true
  • false means false
  • No value means true
  • Empty string means true (wat?)
  • 0 means false
  • null or undefined means the attribute isn’t rendered

Clear as mud, right?

XSS Protection: The One Good Thing

Okay, I’ll give React this one: JSX automatically escapes values to prevent XSS attacks:

const userInput = '<script>alert("XSS")</script>';
return <div>{userInput}</div>;
// Renders as text, not as a script tag

This is genuinely good. Of course, when you actually want to render HTML, you need to use dangerouslySetInnerHTML:

<div dangerouslySetInnerHTML= />

Yes, that’s the actual prop name. It’s like React is passive-aggressively saying, “Fine, render your HTML, but we’re going to make you type ‘dangerous’ every time so you know we disapprove.”

The Reality Check

Here’s what we gave up for JSX:

<!-- Before: Actual HTML -->
<div class="card">
  <h2>Title</h2>
  <p>Content</p>
  <!-- Easy comment -->
  <button onclick="handleClick()">Click</button>
</div>

Here’s what we got:

// After: JSX
<div className="card">
  <h2>Title</h2>
  <p>Content</p>
  {/* Complicated comment */}
  <button onClick={handleClick}>Click</button>
</div>

The differences seem small, but they add up. Every HTML attribute you know needs to be relearned. Every pattern you’re familiar with needs to be translated. Every simple thing becomes slightly more complex.

In Defense of JSX (It Hurts to Say This)

JSX does have some benefits:

  1. Component Composition: Combining components is intuitive
  2. Type Checking: With TypeScript, you get prop validation
  3. JavaScript Power: Full programming language in your templates
  4. Tooling Support: Excellent IDE support and error messages

But we could have had these benefits without creating a whole new syntax that’s neither HTML nor JavaScript.

The Alternatives That Could Have Been

Other frameworks figured this out:

<!-- Vue: Actual HTML with directives -->
<div v-if="isVisible" @click="handleClick">
  
</div>
<!-- Svelte: HTML with minimal additions -->
{#if isVisible}
  <div on:click={handleClick}>
    {message}
  </div>
{/if}

Both are closer to HTML. Both are easier to learn. Both don’t require translating attribute names in your head.

Accepting Your Fate

JSX is here to stay. It’s become so synonymous with React that most developers can’t imagine React without it. Even though you can use React without JSX:

// React without JSX (nobody does this)
React.createElement('div', { className: 'hello' },
  React.createElement('h1', null, 'Hello, World')
)

But nobody does this because it’s verbose and painful. Which is ironic, because that’s exactly what your JSX becomes after transpilation.

The Path Forward

You’re going to write JSX. You’re going to forget that it’s className not class. You’re going to mess up the style object syntax. You’re going to wonder why your comment broke everything.

And eventually, Stockholm syndrome will set in. You’ll start to like JSX. You’ll defend it in forums. You’ll say things like “it’s just JavaScript!” and mean it.

That’s when you’ll know React has won. When the abnormal becomes normal. When the complex becomes “simple.” When writing HTML in JavaScript with different attribute names and special syntax for everything seems perfectly reasonable.

Welcome to JSX. It’s like HTML and JavaScript had a baby, and that baby was raised by wolves who really liked curly braces.

In the next chapter, we’ll explore components - React’s answer to the question “What if everything was a function that returned other functions that returned HTML-ish JavaScript?”

But first, take a moment to remember when HTML was HTML and JavaScript was JavaScript, and never the twain shall meet. Those were simpler times.

Now excuse me while I debug why class="container" isn’t working. Oh right, className. Gets me every time.