Deep dive into JavaScript XML (JSX): syntactic compilation to React.createElement, fundamental rules of JSX, dynamic expression evaluation, multi-branch conditional rendering, rendering lists with stable keys, React Fragments, camelCase inline styling, CSS class composition, component rendering, and clean JSX commenting.
JSX stands for JavaScript XML. It is a syntax extension for JavaScript that allows you to write HTML-like markup directly inside your JavaScript files. Although JSX visually resembles HTML, it is not HTML and the browser cannot execute it natively.
Before running in the browser, build tools (such as Vite with esbuild or Babel) compile every JSX tag into standard JavaScript function calls: React.createElement() (or the modern jsx-runtime).
const element = (
<h1 className="hero-title" id="main-heading">
Hello, <span>World</span>!
</h1>
);
Intuitive, readable, visually describes the intended UI structure.
const element = React.createElement(
"h1",
{ className: "hero-title", id: "main-heading" },
"Hello, ",
React.createElement("span", null, "World"),
"!"
);
Returns a lightweight JavaScript object called a React Element.
Calling React.createElement() produces a plain JavaScript object:
{
type: "h1",
props: {
className: "hero-title",
id: "main-heading",
children: ["Hello, ", { type: "span", props: { children: "World" } }, "!"]
},
key: null,
ref: null
}
React uses these element trees to construct and reconcile the Virtual DOM before committing minimal patches to the real browser DOM.
Because JSX compiles into JavaScript function calls and object expressions, it enforces three strict syntactic rules:
A component must return exactly one top-level root element. If you need to render multiple siblings, wrap them in a parent <div> or a React Fragment (<>...</>).
Rationale: A JavaScript function cannot return multiple values simultaneously without grouping them into an array or object.
In standard HTML, tags like <img>, <input>, and <br> can be left unclosed. In JSX, all self-closing tags must end with a forward slash:
<img src="avatar.jpg" alt="Profile" />
<input type="text" />
<br />
Because JSX is closer to JavaScript than HTML, HTML attributes become camelCase JavaScript object keys:
class becomes className (since class is a reserved JS keyword).for becomes htmlFor.tabindex becomes tabIndex.onclick becomes onClick.
JSX allows you to inject dynamic JavaScript variables, mathematical calculations, function calls, and property lookups directly into your markup using curly braces { ... }.
function UserBadge() {
const user = { firstName: "Sarah", lastName: "Connor", role: "Administrator" };
const notificationCount = 5;
const getGreeting = () => "Welcome back";
return (
<div className="badge-card">
{/* 1. String variable interpolation */}
<h3>{getGreeting()}, {user.firstName} {user.lastName}!</h3>
{/* 2. Embedded JavaScript math */}
<p>Active session duration: {12 * 5} minutes</p>
{/* 3. Passing dynamic attribute props */}
<span title={`Role: ${user.role}`}>
Notifications: {notificationCount * 2}
</span>
</div>
);
}
You can only place JavaScript expressions (code that evaluates to a value) inside JSX curly braces. You cannot place JavaScript statements:
• Allowed (Expressions): {user.name}, {1 + 1}, {isActive ? "Yes" : "No"}, {formatDate(date)}.
• Forbidden (Statements): {if (x > 10) { return true; }}, {for (let i = 0; i < 5; i++) { ... }}. Statements do not return values and cause compilation syntax errors.
React allows you to render completely different markup branches based on dynamic state or props. There are four primary patterns:
Ideal for choosing between two distinct UI elements:
function AuthButton({ isLoggedIn }) {
return (
<button>
{isLoggedIn ? "Log Out" : "Sign In"}
</button>
);
}
Ideal for rendering an element when true, and nothing when false:
function NotificationBanner({ hasUnread, count }) {
return (
<div>
{/* Boolean coercion prevents the number 0 from rendering */}
{hasUnread && count > 0 && (
<span className="badge">{count} New Messages</span>
)}
</div>
);
}
Return early from the function before rendering the main UI:
function ProductDetails({ product, isLoading, error }) {
if (isLoading) return <div>Loading inventory...</div>;
if (error) return <div>Failed to load product details.</div>;
if (!product) return null; // Renders nothing!
return <h1>{product.name} - {product.price} USD</h1>;
}
Pre-calculating elements into a variable before the return statement:
function Dashboard({ role }) {
let adminControls = null;
if (role === "admin") {
adminControls = <button>Delete Server Database</button>;
}
return (
<div>
<h2>Workspace Dashboard</h2>
{adminControls}
</div>
);
}
To render dynamic collections of data (such as product catalogs, comment threads, or user directories), React utilizes JavaScript's native map() method.
const techStack = [
{ id: "react", name: "React", category: "Frontend" },
{ id: "vite", name: "Vite", category: "Build Tool" },
{ id: "node", name: "Node.js", category: "Runtime" }
];
function TechList() {
return (
<ul>
{techStack.map(tech => (
<li key={tech.id}>
<strong>{tech.name}</strong> — {tech.category}
</li>
))}
</ul>
);
}
Whenever you render a list of elements, React requires a unique key string or number on the outermost element returned by the map function.
Keys give each list item a permanent, stable identity across re-renders. During reconciliation, React matches keys in the new Virtual DOM tree with keys in the old Virtual DOM tree:
Using key={index} is an anti-pattern that leads to severe rendering glitches:
// DANGEROUS ANTI-PATTERN:
{items.map((item, index) => (
<TodoItem key={index} text={item.text} />
))}
If items are sorted, filtered, or deleted, their array indices shift! React mistakenly associates the old item's internal state with the new item at that index. Always use unique database IDs (e.g., item.id).
Because JSX components must return a single root element, developers historically wrapped siblings inside unnecessary wrapper <div> tags.
Excessive wrapping <div> elements bloat the browser DOM tree, degrade rendering performance, and completely break CSS Flexbox, CSS Grid layouts, and HTML table structures (e.g., wrapping multiple <td> elements inside a <div> creates invalid HTML).
A Fragment lets you group multiple children together without creating an extra DOM node in the browser:
The standard shorthand used in 95% of components:
function PageHeader() {
return (
<>
<h1>Main Title</h1>
<p>Subheading text without a wrapper div!</p>
</>
);
}
Required when rendering a list of fragments that require a key:
import { Fragment } from "react";
function TermList({ terms }) {
return (
<dl>
{terms.map(t => (
<Fragment key={t.id}>
<dt>{t.term}</dt>
<dd>{t.description}</dd>
</Fragment>
))}
</dl>
);
}
React provides multiple methodologies for styling user interface components:
Unlike HTML where style="..." takes a plain string, React's style attribute accepts a JavaScript object. This requires double curly braces: the outer pair enters JavaScript expression mode, and the inner pair defines the object literal:
function Card({ isUrgent }) {
return (
<div style={{
backgroundColor: isUrgent ? "#fee2e2" : "#ffffff",
fontSize: "14px", // camelCase property
padding: "16px 20px",
borderRadius: "8px", // camelCase
border: "1px solid #e2e8f0"
}}>
Critical System Notice
</div>
);
}
background-color becomes backgroundColor).fontSize: 16 automatically append px).
The standard industry approach for production codebases. You define classes in external CSS files or CSS Modules and attach them via className:
// App.jsx
import "./Button.css";
function ActionButton({ variant = "primary", isActive }) {
// Composing classes dynamically
const btnClass = `btn btn-${variant} ${isActive ? "active" : ""}`;
return (
<button className={btnClass}>
Submit Form
</button>
);
}
Enables full CSS features: media queries, pseudo-classes (:hover, :focus), and keyframe animations that inline styles cannot handle.
Components can be rendered by using their function name as a self-closing or opening/closing tag inside JSX:
// 1. Defining a reusable container component
function ModalContainer({ title, children }) {
return (
<div className="modal-backdrop">
<div className="modal-dialog">
<h2>{title}</h2>
{/* The special 'children' prop renders nested content */}
<div className="modal-body">{children}</div>
</div>
</div>
);
}
// 2. Composing the Modal in an App component
export default function App() {
return (
<main>
<ModalContainer title="User Authentication">
<p>Please enter your single sign-on credentials below:</p>
<input type="email" placeholder="user@company.com" />
<button>Sign In</button>
</ModalContainer>
</main>
);
}
React uses the first letter of the tag name to determine how to compile it:
<div>, <button>, <span>): React treats these as built-in browser DOM elements and passes them as strings to React.createElement("div").<UserProfile>, <Card>): React treats these as custom functional components and passes the direct function identifier to React.createElement(UserProfile).Because JSX sits at the intersection of JavaScript and HTML, writing comments requires a specific syntax:
function CommentDemo() {
// 1. Standard single-line JavaScript comment (outside JSX return)
/* Multi-line JS comment outside JSX */
return (
<div>
{/* 2. Standard single-line comment INSIDE JSX markup */}
<h1>Main Dashboard</h1>
{/*
3. Multi-line comment inside JSX markup:
Explaining complex UI layout structures
across multiple paragraphs.
*/}
<p>Content visible to users.</p>
</div>
);
}
To comment inside JSX markup, you must enter JavaScript expression mode via curly braces {/* ... */} and use standard C-style multi-line comment delimiters.
Below is a complete, self-contained modern React component file demonstrating all concepts learned in this unit: JSX expressions, conditional branches, dynamic list mapping with unique keys, Fragments, inline styles, dynamic class names, and component composition.
import { Fragment, useState } from "react";
// Sub-component 1: Status indicator with inline styles
function PriorityPill({ priority }) {
const isHigh = priority === "High";
return (
<span style={{
padding: "2px 8px",
borderRadius: "4px",
fontSize: "11px",
fontWeight: 700,
backgroundColor: isHigh ? "#fee2e2" : "#e0f2fe",
color: isHigh ? "#b91c1c" : "#0369a1"
}}>
{priority} Priority
</span>
);
}
// Sub-component 2: Reusable task item card
function TaskCard({ id, title, completed, priority, onToggle }) {
return (
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "12px 16px",
borderBottom: "1px solid #f1f5f9"
}}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<input
type="checkbox"
checked={completed}
onChange={() => onToggle(id)}
/>
<span style={{
textDecoration: completed ? "line-through" : "none",
color: completed ? "#94a3b8" : "#1e293b"
}}>
{title}
</span>
</div>
<PriorityPill priority={priority} />
</div>
);
}
// Primary Root Component
export default function ProjectDashboard() {
const [tasks, setTasks] = useState([
{ id: "task-01", title: "Configure Vite build pipeline", completed: true, priority: "High" },
{ id: "task-02", title: "Implement Virtual DOM unit tests", completed: false, priority: "High" },
{ id: "task-03", title: "Write component styling architecture", completed: false, priority: "Normal" }
]);
const [showCompletedOnly, setShowCompletedOnly] = useState(false);
const toggleTask = (taskId) => {
setTasks(prev => prev.map(t =>
t.id === taskId ? { ...t, completed: !t.completed } : t
));
};
// Filter tasks based on conditional filter toggle
const visibleTasks = showCompletedOnly
? tasks.filter(t => t.completed)
: tasks;
const completedCount = tasks.filter(t => t.completed).length;
return (
<>
<header style={{ marginBottom: "20px" }}>
<h1 style={{ fontSize: "22px", color: "#0f172a" }}>
Engineering Sprint Dashboard
</h1>
{/* Conditional rendering with ternary */}
<p style={{ color: "#64748b", fontSize: "13px" }}>
{completedCount === tasks.length
? "🎉 All sprint tasks completed successfully!"
: `Completed ${completedCount} of ${tasks.length} total deliverables.`}
</p>
</header>
<div style={{
background: "#ffffff",
border: "1px solid #e2e8f0",
borderRadius: "10px",
overflow: "hidden"
}}>
<div style={{ padding: "12px 16px", background: "#f8fafc", borderBottom: "1px solid #e2e8f0" }}>
<label style={{ fontSize: "12.5px", color: "#334155", cursor: "pointer" }}>
<input
type="checkbox"
checked={showCompletedOnly}
onChange={() => setShowCompletedOnly(!showCompletedOnly)}
style={{ marginRight: "8px" }}
/>
Show Completed Tasks Only
</label>
</div>
{/* Dynamic list rendering using map and unique keys */}
{visibleTasks.length > 0 ? (
visibleTasks.map(task => (
<TaskCard
key={task.id}
id={task.id}
title={task.title}
completed={task.completed}
priority={task.priority}
onToggle={toggleTask}
/>
))
) : (
<div style={{ padding: "20px", textAlign: "center", color: "#94a3b8" }}>
No tasks match the active filter.
</div>
)}
</div>
</>
);
}
Answer: Browsers cannot run JSX natively because JSX is not part of the standard ECMAScript specification. During the project build process, compilers like Babel or esbuild transform JSX tags into standard JavaScript function calls: React.createElement(type, props, ...children). Calling this function produces a lightweight plain JavaScript object known as a React Element, which describes the desired DOM node to the Virtual DOM engine.
Answer: Under the hood, JSX compiles into a standard JavaScript function call (React.createElement). Since a JavaScript function cannot return multiple return values simultaneously without wrapping them into a single data structure, returning multiple sibling JSX tags without a parent container triggers a syntax error.
React Fragments (<>...</> or <Fragment>...</Fragment>) solve this by allowing you to group multiple child elements together without injecting an unnecessary wrapper <div> into the actual physical browser DOM.
Answer: The key prop provides a stable identity for React to match list items between renders during reconciliation. If you use array indices (key={index}) and items are reordered, inserted at the top, or filtered, their indices shift dynamically. React identifies elements purely by key and component type; if the keys shift, React mistakenly attaches previous local component state or DOM input values to the wrong data items, causing subtle visual and data-corruption bugs.
Answer: Because JSX is a syntax extension of JavaScript rather than HTML, attribute names in JSX are compiled directly into property keys of a JavaScript object (the props object passed to React.createElement). In JavaScript, class is a reserved keyword used for declaring ES6 classes, and for is a reserved keyword for loops. To prevent syntactic ambiguity, React uses standard DOM API property naming: className and htmlFor.
Answer: In JavaScript, the expression condition && <Component /> relies on short-circuit evaluation: if the left operand is truthy, it evaluates and returns the right operand. However, if the left operand is falsy, JavaScript returns the value of the left operand itself! If you write {items.length && <List />} and the array is empty, items.length evaluates to the number 0. Because 0 is a valid renderable value, React actually prints a stray literal 0 onto the browser screen! To prevent this, always ensure the left-hand side is a strict boolean: {items.length > 0 && <List />} or {Boolean(items.length) && ...}.
Answer: In HTML, inline styles are written as hyphenated strings (e.g., style="font-size: 14px; background-color: red;"). In React, the style attribute accepts a JavaScript object where CSS property names must be written in camelCase (e.g., style={{ fontSize: "14px", backgroundColor: "red" }}). Pixel values can be supplied as raw numbers, and dynamic variables can be injected directly without string concatenation.
React.createElement() function calls producing lightweight Virtual DOM element objects.<img />), and write attributes in camelCase (className, htmlFor).{ ... } to evaluate variables, calculations, and ternary branches; statements (like if or for) are forbidden inside JSX markup.<>...</> to group siblings without bloating the browser DOM with redundant wrapper divs.