JSX
Not HTML: What React Actually Compiles and Why It Matters
LinkedIn Hook
You write
<div class="container">in a React component.The app renders. No error. But your styles don't apply.
You spend 20 minutes debugging CSS before realizing — React uses
className, notclass.Here's the thing: JSX looks like HTML, but it is NOT HTML. It's syntactic sugar for JavaScript function calls. Every
<div>you write gets compiled toReact.createElement("div", ...)— and that distinction changes everything.In this lesson, I break down what JSX actually compiles to, the rules that trip up every developer (className, htmlFor, camelCase), how to embed expressions, the 3 patterns for conditional rendering, and why the
keyprop exists — and what happens when you get it wrong.If an interviewer asks "what is JSX?" and you answer "it's basically HTML" — you've already lost points.
Read the full lesson → [link]
#React #JavaScript #WebDevelopment #InterviewPrep #Frontend #CodingInterview #JSX #100DaysOfCode
What You'll Learn
- What JSX actually compiles to under the hood (React.createElement)
- The JSX rules that differ from HTML (className, htmlFor, camelCase attributes)
- How to embed JavaScript expressions inside JSX
- Three conditional rendering patterns and when to use each
- Why the
keyprop matters in lists and what goes wrong without it
JSX Is a Translation Layer, Not a Template Language
The Analogy
Imagine you're writing a letter in shorthand. You write quick, abbreviated notes that make sense to you — but before the letter gets mailed, a translator converts every shorthand symbol into proper, full English sentences.
JSX works the same way. You write <h1>Hello</h1> — that's the shorthand. But React never sends that to the browser. A compiler (Babel) translates your shorthand into React.createElement("h1", null, "Hello") — the full JavaScript that React actually executes.
HTML lives in .html files and the browser parses it directly. JSX lives in .js files and must be compiled before the browser ever sees it. That single difference is why JSX has its own rules.
What JSX Compiles To
Every piece of JSX is a React.createElement() call. Understanding this removes all the mystery.
// What you write (JSX)
const element = <h1 className="title">Hello, World!</h1>;
// What Babel compiles it to (JavaScript)
const element = React.createElement(
"h1", // type: the HTML tag or component
{ className: "title" }, // props: an object of attributes
"Hello, World!" // children: content inside the tag
);
// What React.createElement returns (a plain object)
// {
// type: "h1",
// props: {
// className: "title",
// children: "Hello, World!"
// }
// }
Nested elements become nested calls:
// JSX
const app = (
<div>
<h1>Title</h1>
<p>Paragraph</p>
</div>
);
// Compiles to
const app = React.createElement(
"div",
null,
React.createElement("h1", null, "Title"),
React.createElement("p", null, "Paragraph")
);
This is why JSX must have a single parent element — React.createElement takes one type as its first argument. You can't return two sibling calls without wrapping them.
Note: Since React 17, the new JSX transform imports
jsxfromreact/jsx-runtimeautomatically — you no longer needimport React from "react"at the top of every file. But the concept is identical: JSX still compiles to function calls that produce plain objects.
JSX Rules — Where HTML Habits Break
Since JSX is JavaScript, not HTML, certain HTML attributes collide with JavaScript reserved words or conventions. Here are the rules you must know.
className, Not class
// WRONG — "class" is a reserved word in JavaScript
<div class="container">Hello</div>
// CORRECT — use className
<div className="container">Hello</div>
htmlFor, Not for
// WRONG — "for" is a reserved word in JavaScript (for loops)
<label for="email">Email</label>
// CORRECT — use htmlFor
<label htmlFor="email">Email</label>
<input id="email" type="email" />
camelCase for All Attributes
HTML attributes are case-insensitive. JSX attributes are JavaScript object keys — they follow camelCase.
// HTML style // JSX equivalent
// onclick="handleClick()" → onClick={handleClick}
// tabindex="0" → tabIndex={0}
// maxlength="100" → maxLength={100}
// readonly → readOnly
// autofocus → autoFocus
// contenteditable → contentEditable
style Takes an Object, Not a String
// HTML
// <div style="background-color: red; font-size: 16px;">
// JSX — double curly braces: outer = expression, inner = object
<div style={{ backgroundColor: "red", fontSize: "16px" }}>
Styled content
</div>
// CSS property names become camelCase: background-color → backgroundColor
Self-Closing Tags Are Required
In HTML, <img>, <br>, <input> can be left unclosed. In JSX, every tag must close.
// WRONG in JSX
<img src="photo.jpg">
<br>
<input type="text">
// CORRECT in JSX
<img src="photo.jpg" />
<br />
<input type="text" />
Boolean Attributes
// In HTML, presence alone means true: <input disabled>
// In JSX, you can do the same or be explicit:
<input disabled /> // disabled = true (implicit)
<input disabled={true} /> // same thing, explicit
<input disabled={false} /> // NOT disabled
Expressions in JSX
JSX lets you embed any JavaScript expression inside curly braces {}. An expression is anything that produces a value — a variable, a function call, a math operation, a ternary.
const name = "Rakibul";
const age = 25;
function ProfileCard() {
return (
<div>
{/* Variable */}
<h1>{name}</h1>
{/* Expression */}
<p>Next year: {age + 1}</p>
{/* Function call */}
<p>{name.toUpperCase()}</p>
{/* Ternary */}
<p>{age >= 18 ? "Adult" : "Minor"}</p>
{/* Template literal */}
<p>{`Hello, ${name}!`}</p>
</div>
);
}
// Output:
// <h1>Rakibul</h1>
// <p>Next year: 26</p>
// <p>RAKIBUL</p>
// <p>Adult</p>
// <p>Hello, Rakibul!</p>
What You Cannot Put in Curly Braces
Statements do not produce values — you cannot use if, for, while, or switch directly inside JSX.
// WRONG — if is a statement, not an expression
<div>
{if (loggedIn) { return "Welcome" }}
</div>
// CORRECT — use a ternary (expression) or move the logic outside
<div>
{loggedIn ? "Welcome" : "Please log in"}
</div>
Rendering Arrays
JSX can render arrays of elements. This is how lists work.
const fruits = ["Apple", "Banana", "Cherry"];
function FruitList() {
return (
<ul>
{fruits.map((fruit, index) => (
<li key={fruit}>{fruit}</li>
))}
</ul>
);
}
// Output:
// <ul>
// <li>Apple</li>
// <li>Banana</li>
// <li>Cherry</li>
// </ul>
Values JSX Ignores
React silently skips certain values — this is a feature, not a bug. It enables conditional rendering patterns.
<div>
{true} {/* renders nothing */}
{false} {/* renders nothing */}
{null} {/* renders nothing */}
{undefined} {/* renders nothing */}
{"hello"} {/* renders "hello" */}
{0} {/* renders "0" — this catches people off guard! */}
{""} {/* renders nothing */}
</div>
The fact that 0 renders but false does not is the source of a very common bug. We'll see it in the next section.
Conditional Rendering Patterns
Since you can't use if statements inside JSX, React developers use three patterns for conditional rendering. Interviewers love asking about the differences.
Pattern 1: Ternary Operator (if-else)
Use when you need to render one thing OR another.
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please sign in.</h1>
)}
</div>
);
}
// isLoggedIn = true → "Welcome back!"
// isLoggedIn = false → "Please sign in."
Pattern 2: Logical AND (&&) — Show or Hide
Use when you want to render something OR nothing.
function Notification({ hasMessages, count }) {
return (
<div>
{hasMessages && <span>You have new messages!</span>}
</div>
);
}
// hasMessages = true → renders the span
// hasMessages = false → renders nothing (false is ignored by JSX)
The && trap with numbers — an interview favorite:
function MessageCount({ count }) {
return (
<div>
{/* BUG: when count is 0, this renders "0" on screen! */}
{count && <span>You have {count} messages</span>}
{/* FIX: convert to boolean first */}
{count > 0 && <span>You have {count} messages</span>}
{/* Or use Boolean() */}
{Boolean(count) && <span>You have {count} messages</span>}
{/* Or use double negation */}
{!!count && <span>You have {count} messages</span>}
</div>
);
}
// count = 5 → renders "You have 5 messages"
// count = 0 → BUG line renders "0", FIX lines render nothing
Why does this happen? The && operator returns the first falsy value. When count is 0, 0 && <span>... returns 0 — and React renders the number 0 on screen. It does NOT render false, null, or undefined, but it DOES render 0.
Pattern 3: Early Return
Use when an entire component should not render, or when you want to handle edge cases before the main return.
function Dashboard({ user, isLoading }) {
// Early return for loading state
if (isLoading) {
return <p>Loading...</p>;
}
// Early return for no user
if (!user) {
return <p>Please log in to view dashboard.</p>;
}
// Main render — only reached if loaded and user exists
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>Email: {user.email}</p>
</div>
);
}
Early return keeps the main JSX clean — no deeply nested ternaries or && chains.
Why key Matters in Lists
When you render a list with .map(), React requires a key prop on each element. This is not a suggestion — it directly affects how React updates the DOM.
The Analogy
Imagine a teacher with a class of 30 students. If every student wears a name tag (a unique key), the teacher can instantly identify who's new, who left, and who moved seats. Without name tags, the teacher has to compare faces one by one with a class photo — slower and error-prone.
React's reconciliation algorithm works the same way. Keys are name tags for list items.
How React Uses Keys
// Without key — React uses index by default (and warns you)
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}> {/* unique, stable key */}
{todo.text}
</li>
))}
</ul>
);
}
When the list changes (item added, removed, or reordered), React compares old keys to new keys:
- Same key, same position — keep the DOM node, update props if changed
- Same key, different position — move the DOM node (not recreate)
- New key — create a new DOM node
- Missing key — remove the DOM node
Why Index as Key Is Dangerous
// DANGEROUS — using index as key
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
Suppose you have three items: ["A", "B", "C"] with keys [0, 1, 2].
You delete "A". Now the array is ["B", "C"] with keys [0, 1].
React sees: key 0 had "A", now key 0 has "B" — it thinks key 0 changed content. Key 1 had "B", now has "C" — another content change. Key 2 is gone — remove it.
React updated two DOM nodes and removed one. The correct behavior (remove key "A", keep "B" and "C") would have touched one node.
Worse: if list items have local state (like input fields), the state sticks to the index, not the item. Deleting the first item shifts all state up by one — inputs show the wrong values.
What Makes a Good Key
// GOOD — unique, stable identifier from your data
<li key={user.id}>{user.name}</li>
<li key={product.sku}>{product.name}</li>
<li key={email}>{email}</li>
// OK — only if list is static and never reordered
<li key={index}>{staticItem}</li>
// BAD — random values create new keys every render
<li key={Math.random()}>{item}</li> // new key each render = full remount
<li key={crypto.randomUUID()}>{item}</li> // same problem
Rules for keys:
- Unique among siblings — keys must be unique within the same list, not globally
- Stable — the same item must produce the same key across re-renders
- Not index — unless the list is static and never reordered or filtered
- Not random — random keys destroy and recreate every element on every render
Common Mistakes
1. Using class Instead of className
This is the most common mistake when switching from HTML to JSX. The code still runs — React even shows a warning in the console — but your CSS classes won't apply. Always use className.
2. The 0 && Rendering Bug
Writing {count && <Component />} when count can be 0 renders a literal 0 on screen. Always convert to a proper boolean: {count > 0 && <Component />} or use a ternary.
3. Using Random or Unstable Keys in Lists
Using Math.random() as a key forces React to destroy and recreate every list item on every re-render — destroying local state, killing performance, and breaking animations. Always use a stable identifier from your data.
Interview Questions
Q: What is JSX and what does it compile to?
Q: Why can't you use class and for attributes in JSX?
Because
classandforare reserved keywords in JavaScript. Since JSX is JavaScript syntax, they would cause conflicts. React usesclassNameandhtmlForinstead. These map to the same DOM properties —element.classNameandlabel.htmlForare the actual DOM API names anyway.
Q: What's the difference between {count && <Component />} and {count > 0 && <Component />}?
Q: Why does React need the key prop in lists? What happens if you use the array index as the key?
Q: Can you use if-else inside JSX? If not, what alternatives do you use?
You cannot use
if-elsedirectly inside JSX because JSX only accepts expressions, andifis a statement. The three alternatives are: ternary operator (condition ? A : B), logical AND (condition && element), and early return (handling conditions before the main return statement).
Quick Reference -- Cheat Sheet
JSX RULES AT A GLANCE
=====================
HTML Attribute → JSX Attribute
─────────────────────────────────────
class → className
for → htmlFor
onclick → onClick
tabindex → tabIndex
maxlength → maxLength
style="..." → style={{ ... }}
<img> → <img />
<br> → <br />
WHAT JSX COMPILES TO
====================
<Tag prop="val">child</Tag>
↓
React.createElement(Tag, { prop: "val" }, "child")
CONDITIONAL RENDERING
=====================
if-else → ternary: {cond ? <A/> : <B/>}
show/hide → logical AND: {cond && <A/>}
guard clause → early return: if (!x) return <Fallback/>
DANGER: {0 && <X/>} renders "0" — use {n > 0 && <X/>}
KEY RULES
=========
- Must be unique among siblings
- Must be stable across re-renders
- Use data IDs, not array index (unless static list)
- NEVER use Math.random() as key
Previous: Lesson 1.2 -- Virtual DOM & Reconciliation Next: Lesson 1.4 -- Component Types & Composition
This is Lesson 1.3 of the React Interview Prep Course -- 10 chapters, 42 lessons.