Episode 2 — React Frontend Architecture NextJS / 2.1 — Introduction to React
2.1.g — JSX Syntax Rules
In one sentence: JSX is a syntax extension that lets you write HTML-like markup inside JavaScript, which gets compiled to
React.createElement()calls that produce the Virtual DOM objects React uses for rendering.
Navigation: ← Project Structure Breakdown · Next → Component-Based Architecture
1. What Is JSX
JSX stands for JavaScript XML. It is a syntax extension for JavaScript that looks like HTML but is not HTML. It was created by the React team to make describing UI structure intuitive.
// This is JSX:
const element = <h1 className="title">Hello, world!</h1>;
// It looks like HTML, but it's JavaScript.
// This line is valid JavaScript (after compilation).
// It creates a React element — a plain JavaScript object.
JSX is NOT:
- HTML (different attribute names, different rules)
- A template language (like Handlebars, EJS, Pug)
- A string (no quotes around it)
- Required (you can write React without JSX)
JSX IS:
- Syntactic sugar for
React.createElement()calls - Compiled to JavaScript before running in the browser
- A way to describe UI structure inside JavaScript
2. JSX Is NOT HTML
This is the most common misconception. JSX looks like HTML but has important differences:
// HTML:
<div class="container">
<label for="email">Email</label>
<input type="email" tabindex="1" autofocus>
<img src="logo.png">
<p style="color: red; font-size: 16px;">Error</p>
</div>
// JSX (same structure, different syntax):
<div className="container">
<label htmlFor="email">Email</label>
<input type="email" tabIndex="1" autoFocus />
<img src="logo.png" />
<p style={{ color: 'red', fontSize: '16px' }}>Error</p>
</div>
Key Differences Summary
| HTML | JSX | Why |
|---|---|---|
class | className | class is a reserved word in JavaScript |
for | htmlFor | for is a reserved word in JavaScript (for loops) |
tabindex | tabIndex | JSX uses camelCase for all attributes |
autofocus | autoFocus | camelCase |
onclick | onClick | camelCase |
onchange | onChange | camelCase |
maxlength | maxLength | camelCase |
readonly | readOnly | camelCase |
style="color: red" | style={{ color: 'red' }} | Object, not string |
<input> | <input /> | All tags must be closed |
<img> | <img /> | Self-closing tags need / |
<br> | <br /> | Self-closing |
<!-- comment --> | {/* comment */} | JS expression syntax |
3. How JSX Compiles
JSX is not valid JavaScript. It must be compiled (by Babel or SWC) into regular JavaScript before the browser can run it.
The Compilation Step
// BEFORE compilation (what you write):
const element = (
<div className="card">
<h1>Hello</h1>
<p>Welcome to React</p>
</div>
);
// AFTER compilation (what the browser runs):
// Classic runtime (React 16 and earlier):
const element = React.createElement(
'div',
{ className: 'card' },
React.createElement('h1', null, 'Hello'),
React.createElement('p', null, 'Welcome to React')
);
// Automatic runtime (React 17+, used by Vite):
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
const element = _jsxs('div', {
className: 'card',
children: [
_jsx('h1', { children: 'Hello' }),
_jsx('p', { children: 'Welcome to React' }),
],
});
React.createElement Signature
React.createElement(
type, // 'div', 'h1', 'p', or a component function/class
props, // { className: 'card', onClick: fn } or null
...children // Child elements or text content
);
// Returns a plain JavaScript object:
{
$$typeof: Symbol.for('react.element'),
type: 'div',
key: null,
ref: null,
props: {
className: 'card',
children: [
{ type: 'h1', props: { children: 'Hello' } },
{ type: 'p', props: { children: 'Welcome to React' } }
]
}
}
Why the Automatic Runtime Matters
In the classic runtime, you had to import React in every file that used JSX:
// Classic runtime: REQUIRED import
import React from 'react';
function App() {
return <h1>Hello</h1>;
// Compiles to: React.createElement('h1', null, 'Hello')
// "React" must be in scope for this to work!
}
In the automatic runtime (React 17+, default in Vite):
// Automatic runtime: NO import needed
function App() {
return <h1>Hello</h1>;
// Compiles to: _jsx('h1', { children: 'Hello' })
// The compiler automatically inserts the import
}
This is configured in vite.config.js → the React plugin handles it.
4. JSX ↔ createElement Equivalence
Understanding this mapping is fundamental. Every piece of JSX has an exact createElement equivalent.
Simple Element
// JSX:
<button>Click me</button>
// createElement:
React.createElement('button', null, 'Click me')
Element with Props
// JSX:
<button className="primary" disabled>Click me</button>
// createElement:
React.createElement('button', {
className: 'primary',
disabled: true
}, 'Click me')
Nested Elements
// JSX:
<div>
<h1>Title</h1>
<p>Content</p>
</div>
// createElement:
React.createElement('div', null,
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Content')
)
Component Element
// JSX:
<UserCard name="Alice" age={30} />
// createElement:
React.createElement(UserCard, { name: 'Alice', age: 30 })
// Note: UserCard is a variable reference, not a string
Expression Children
// JSX:
<p>Hello, {name}!</p>
// createElement:
React.createElement('p', null, 'Hello, ', name, '!')
Event Handlers
// JSX:
<button onClick={() => alert('clicked')}>Click</button>
// createElement:
React.createElement('button', {
onClick: () => alert('clicked')
}, 'Click')
Conditional Element
// JSX:
{isLoggedIn ? <Dashboard /> : <Login />}
// createElement:
isLoggedIn
? React.createElement(Dashboard, null)
: React.createElement(Login, null)
You never need to write createElement directly. JSX is the standard way to write React. But understanding the mapping helps you understand what JSX actually does.
5. JSX Syntax Rules
Rule 1: Single Root Element
Every JSX expression must have exactly one root element.
// WRONG: Two root elements
function App() {
return (
<h1>Title</h1>
<p>Content</p>
);
}
// Error: Adjacent JSX elements must be wrapped in an enclosing tag
// RIGHT: One root element
function App() {
return (
<div>
<h1>Title</h1>
<p>Content</p>
</div>
);
}
// RIGHT: Use a Fragment to avoid adding a DOM element
function App() {
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
}
Why? JSX compiles to a createElement call. A function call returns ONE value, not two:
// This is one function call, one return value:
React.createElement('div', null,
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Content')
)
// You can't return two function calls without wrapping:
React.createElement('h1', null, 'Title'), // ???
React.createElement('p', null, 'Content') // Two values?
// JavaScript doesn't work this way.
Rule 2: className, Not class
// WRONG:
<div class="container">...</div>
// Warning: Invalid DOM property `class`. Did you mean `className`?
// RIGHT:
<div className="container">...</div>
class is a reserved keyword in JavaScript (used for class declarations). JSX uses className to avoid the conflict.
Rule 3: htmlFor, Not for
// WRONG:
<label for="email">Email</label>
// RIGHT:
<label htmlFor="email">Email</label>
for is a reserved keyword in JavaScript (used for for-loops).
Rule 4: camelCase Attributes
All HTML attributes become camelCase in JSX:
// HTML attributes → JSX attributes
<input
tabIndex={1} // tabindex → tabIndex
autoFocus // autofocus → autoFocus
autoComplete="email" // autocomplete → autoComplete
maxLength={50} // maxlength → maxLength
readOnly // readonly → readOnly
spellCheck={false} // spellcheck → spellCheck
contentEditable={true} // contenteditable → contentEditable
/>
Rule 5: Self-Closing Tags
In HTML, some tags are self-closing (<img>, <br>, <input>). In JSX, ALL self-closing tags must have the closing slash:
// HTML:
<img src="photo.jpg">
<br>
<input type="text">
<hr>
// JSX:
<img src="photo.jpg" />
<br />
<input type="text" />
<hr />
// Tags with children still use opening/closing:
<div>content</div>
<p>text</p>
// Empty tags can self-close:
<div /> // Valid (renders empty div)
<CustomComponent /> // Valid
Rule 6: Style Attribute Is an Object
In HTML, style is a string. In JSX, it's a JavaScript object with camelCase properties:
// HTML:
<p style="color: red; font-size: 16px; background-color: #f0f0f0; margin-top: 20px;">
Text
</p>
// JSX:
<p style={{
color: 'red',
fontSize: '16px', // font-size → fontSize
backgroundColor: '#f0f0f0', // background-color → backgroundColor
marginTop: '20px', // margin-top → marginTop
}}>
Text
</p>
The double braces {{ }} are NOT special syntax. It's a JavaScript object {} inside a JSX expression {}:
// Breaking it down:
const myStyle = {
color: 'red',
fontSize: '16px',
};
<p style={myStyle}>Text</p>
// Equivalent to:
<p style={{ color: 'red', fontSize: '16px' }}>Text</p>
Style Property Names
| CSS Property | JSX Style Property |
|---|---|
font-size | fontSize |
background-color | backgroundColor |
border-radius | borderRadius |
z-index | zIndex |
margin-top | marginTop |
padding-left | paddingLeft |
text-align | textAlign |
font-weight | fontWeight |
line-height | lineHeight |
box-shadow | boxShadow |
text-decoration | textDecoration |
list-style-type | listStyleType |
Numeric Values
// Numbers are treated as pixels for most properties:
<div style={{ width: 100, height: 200, padding: 20 }}>
{/* Equivalent to: width: '100px', height: '200px', padding: '20px' */}
</div>
// Some properties are unitless:
<div style={{
lineHeight: 1.5, // No unit — it's a ratio
opacity: 0.8, // No unit — it's a ratio
zIndex: 10, // No unit — it's a number
flex: 1, // No unit
fontWeight: 700, // No unit
}}>
</div>
6. Embedding JavaScript Expressions
Curly braces {} are the escape hatch from JSX to JavaScript. Anything inside {} is evaluated as a JavaScript expression.
What Can Go Inside {}
// Variables
const name = 'Alice';
<h1>Hello, {name}</h1>
// Output: <h1>Hello, Alice</h1>
// Arithmetic
<p>Total: {price * quantity}</p>
// Function calls
<p>Uppercase: {name.toUpperCase()}</p>
<p>Date: {new Date().toLocaleDateString()}</p>
<p>Random: {Math.floor(Math.random() * 100)}</p>
// Ternary operator
<p>{isLoggedIn ? 'Welcome back' : 'Please sign in'}</p>
// Template literals
<p>{`${firstName} ${lastName}`}</p>
// Array methods
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
// Object access
<p>{user.name}</p>
<p>{user.address.city}</p>
// Short-circuit evaluation
{showWarning && <p className="warning">Careful!</p>}
// Immediately invoked function
{(() => {
if (score > 90) return 'A';
if (score > 80) return 'B';
return 'C';
})()}
What CANNOT Go Inside {}
// WRONG: Statements are not allowed. Only expressions.
// if/else is a statement, not an expression
{if (isLoggedIn) { return 'Hello'; }} // SyntaxError
// for loop is a statement
{for (let i = 0; i < 5; i++) { ... }} // SyntaxError
// switch is a statement
{switch(role) { case 'admin': ... }} // SyntaxError
// Variable declaration is a statement
{const x = 5} // SyntaxError
// Assignment is a statement (sort of — it returns a value but JSX will
// try to render the return value, which is confusing)
{x = 5} // Don't do this
Expression vs Statement: The Rule
EXPRESSION: Produces a value. Can be used as a function argument.
42
'hello'
1 + 2
isLoggedIn ? 'yes' : 'no'
items.map(i => i.name)
myFunction()
STATEMENT: Performs an action. Cannot be used where a value is expected.
if (x) { ... }
for (let i = 0; ...) { ... }
switch (x) { ... }
const y = 5;
return x;
Quick test: Can you pass it to console.log()?
console.log(1 + 2) // Yes → expression
console.log(if (x) { ... }) // No → statement
7. Conditional Rendering
Since if/else is a statement and cannot be used in JSX directly, React developers use these patterns:
Pattern 1: Logical AND (&&)
// Show element only if condition is true
function Notification({ count }) {
return (
<div>
<h1>Dashboard</h1>
{count > 0 && <span className="badge">{count}</span>}
</div>
);
}
// How it works:
// count > 0 is true → evaluates and returns <span>
// count > 0 is false → short-circuits, returns false
// React ignores false, null, undefined — nothing rendered
Gotcha with && and numbers:
// WRONG: 0 is falsy but React renders it!
{count && <span>{count} items</span>}
// If count is 0, this renders "0" on the screen
// Because 0 is falsy, && returns 0, and React renders the number 0
// RIGHT: Be explicit about the boolean
{count > 0 && <span>{count} items</span>}
// 0 > 0 is false, and React doesn't render false
Pattern 2: Ternary Operator
// Choose between two elements
function AuthButton({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<button onClick={handleLogout}>Log Out</button>
) : (
<button onClick={handleLogin}>Log In</button>
)}
</div>
);
}
// Nested ternaries (avoid more than one level):
{role === 'admin' ? (
<AdminPanel />
) : role === 'editor' ? (
<EditorPanel />
) : (
<ViewerPanel />
)}
// This works but is hard to read. Use a variable instead (see below).
Pattern 3: Early Return
// Return different JSX based on conditions
function UserProfile({ user, isLoading, error }) {
if (isLoading) {
return <Spinner />;
}
if (error) {
return <ErrorMessage error={error} />;
}
if (!user) {
return <p>No user found.</p>;
}
return (
<div className="profile">
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Pattern 4: Variable Assignment
// Compute the element before the return statement
function StatusMessage({ status }) {
let message;
switch (status) {
case 'loading':
message = <Spinner />;
break;
case 'error':
message = <p className="error">Something went wrong</p>;
break;
case 'empty':
message = <p className="empty">No results found</p>;
break;
case 'success':
message = <ResultsList />;
break;
default:
message = null;
}
return (
<div className="status-container">
<h2>Results</h2>
{message}
</div>
);
}
Pattern 5: Object Lookup
// Map values to components using an object
const STATUS_COMPONENTS = {
loading: <Spinner />,
error: <ErrorMessage />,
empty: <EmptyState />,
success: <SuccessMessage />,
};
function StatusDisplay({ status }) {
return (
<div>
{STATUS_COMPONENTS[status] || <p>Unknown status</p>}
</div>
);
}
8. Rendering Lists with map()
The standard pattern for rendering collections in React is the .map() array method:
function UserList({ users }) {
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} ({user.email})
</li>
))}
</ul>
);
}
The key Prop (Mandatory for Lists)
Every element in a mapped list MUST have a unique key prop:
// REQUIRED: key prop on the outermost element in the map callback
// GOOD: Using a unique ID
{users.map(user => (
<UserCard key={user.id} user={user} />
))}
// GOOD: Using a unique string
{tabs.map(tab => (
<Tab key={tab.slug} label={tab.label} />
))}
// ACCEPTABLE: Using index (only if items are static, never reordered)
{staticLabels.map((label, index) => (
<span key={index}>{label}</span>
))}
// BAD: Random key (defeats React's diffing)
{users.map(user => (
<UserCard key={Math.random()} user={user} />
))}
// BAD: No key at all
{users.map(user => (
<UserCard user={user} />
))}
// Warning: Each child in a list should have a unique "key" prop.
Filtering and Mapping
// Filter first, then map
function ActiveUsers({ users }) {
return (
<ul>
{users
.filter(user => user.isActive)
.map(user => (
<li key={user.id}>{user.name}</li>
))
}
</ul>
);
}
// Sort, then map
function SortedList({ items }) {
return (
<ul>
{[...items]
.sort((a, b) => a.name.localeCompare(b.name))
.map(item => (
<li key={item.id}>{item.name}</li>
))
}
</ul>
);
}
Rendering Nested Lists
function CategoryList({ categories }) {
return (
<div>
{categories.map(category => (
<section key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</section>
))}
</div>
);
}
9. JSX and XSS Prevention
JSX automatically escapes values to prevent Cross-Site Scripting (XSS) attacks:
// User input is AUTOMATICALLY escaped
const userInput = '<script>alert("hacked!")</script>';
// This is SAFE:
<p>{userInput}</p>
// Renders as: <p><script>alert("hacked!")</script></p>
// The script tag appears as TEXT, not as executable code
// React escapes:
// < → <
// > → >
// " → "
// ' → '
// & → &
The Dangerous Escape Hatch
If you actually need to render raw HTML (from a trusted source like a CMS), use dangerouslySetInnerHTML:
// DANGEROUS: Only use with sanitized/trusted HTML
const trustedHTML = '<p>This is <strong>bold</strong> text from the CMS.</p>';
<div dangerouslySetInnerHTML={{ __html: trustedHTML }} />
// The name "dangerouslySetInnerHTML" is intentionally scary.
// It reminds you this bypasses React's XSS protection.
// Always sanitize HTML before using this (use a library like DOMPurify).
// SAFE pattern with sanitization:
import DOMPurify from 'dompurify';
function RichContent({ html }) {
const sanitized = DOMPurify.sanitize(html);
return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
}
10. Fragments
Fragments let you group multiple elements without adding an extra DOM node:
Short Syntax (<>)
function UserInfo({ user }) {
return (
<>
<h1>{user.name}</h1>
<p>{user.email}</p>
<p>{user.phone}</p>
</>
);
}
// Renders:
// <h1>Alice</h1>
// <p>alice@example.com</p>
// <p>555-1234</p>
// NO wrapper div!
Long Syntax (React.Fragment) — When You Need Keys
// Short syntax CANNOT have keys
// Use React.Fragment when you need a key (like in a list):
function GlossaryList({ items }) {
return (
<dl>
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
</dl>
);
}
// Or with named import:
import { Fragment } from 'react';
{items.map(item => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</Fragment>
))}
When to Use Fragments
// Problem: Unwanted wrapper elements break CSS layout
// BAD: Extra div breaks flex layout
function NavItems() {
return (
<div> {/* This div is a flex child now, wrapping all items */}
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</div>
);
}
// GOOD: Fragment adds no DOM element
function NavItems() {
return (
<>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</>
);
}
// These render as direct children of the parent flex container
11. JSX Spread Attributes
The spread operator {...obj} passes all properties of an object as props:
// Spreading props
const buttonProps = {
className: 'primary',
disabled: false,
onClick: handleClick,
'aria-label': 'Submit form',
};
// These are equivalent:
<button {...buttonProps}>Submit</button>
<button className="primary" disabled={false} onClick={handleClick} aria-label="Submit form">Submit</button>
Common Use: Forwarding Props
// Pass all props through to a child component
function Card({ children, ...rest }) {
return (
<div className="card" {...rest}>
{children}
</div>
);
}
// Usage:
<Card id="user-card" data-testid="card" onClick={handleClick}>
<p>Content</p>
</Card>
// The Card renders:
// <div className="card" id="user-card" data-testid="card" onClick={handleClick}>
// <p>Content</p>
// </div>
Override Order Matters
// Props are applied left to right — later values override earlier ones
const defaults = { className: 'default', disabled: true };
<button {...defaults} className="custom">
{/* className is "custom" (overrides "default") */}
{/* disabled is true (from spread) */}
</button>
<button className="custom" {...defaults}>
{/* className is "default" (spread overrides "custom") */}
{/* disabled is true (from spread) */}
</button>
12. JSX Comments
// JavaScript comments work outside JSX:
// This is a comment
function App() {
// This works — we're in JavaScript context
return (
<div>
{/* This is a JSX comment — inside curly braces */}
{/*
Multi-line JSX comment
goes like this
*/}
<h1>Title</h1>
{/* Commenting out an element: */}
{/* <p>This paragraph is hidden</p> */}
{/* WRONG: HTML comments don't work in JSX */}
{/* <!-- This won't work --> */}
</div>
);
}
13. Boolean, Null, and Undefined in JSX
React has specific rendering rules for these values:
// These values render NOTHING (empty output):
<div>{true}</div> // <div></div>
<div>{false}</div> // <div></div>
<div>{null}</div> // <div></div>
<div>{undefined}</div> // <div></div>
// These values DO render:
<div>{0}</div> // <div>0</div> ← Watch out!
<div>{''}</div> // <div></div> ← Empty string = nothing
<div>{NaN}</div> // <div>NaN</div> ← Renders the text "NaN"
<div>{[]}</div> // <div></div> ← Empty array = nothing
Boolean Props
// Boolean true can be passed by just writing the prop name:
<input disabled /> // same as disabled={true}
<button autoFocus /> // same as autoFocus={true}
// Boolean false: omit the prop or pass false explicitly
<input disabled={false} /> // explicitly disabled=false
<input /> // no disabled prop at all
// This is a React convention matching HTML boolean attributes
Rendering Booleans as Text
// If you need to show true/false as text:
<p>{String(isActive)}</p> // "true" or "false"
<p>{isActive.toString()}</p> // "true" or "false"
<p>{isActive ? 'Yes' : 'No'}</p> // "Yes" or "No"
14. Common JSX Mistakes and Fixes
Mistake 1: Forgetting to Return JSX
// WRONG: Arrow function with braces needs explicit return
const App = () => {
<div>Hello</div> // No return! This returns undefined.
};
// RIGHT: Add return
const App = () => {
return <div>Hello</div>;
};
// RIGHT: Implicit return (no braces)
const App = () => <div>Hello</div>;
// RIGHT: Implicit return with parentheses (for multi-line)
const App = () => (
<div>
<h1>Hello</h1>
</div>
);
Mistake 2: Using class Instead of className
// WRONG:
<div class="container">
// RIGHT:
<div className="container">
Mistake 3: Forgetting to Close Tags
// WRONG:
<img src="photo.jpg">
<input type="text">
<br>
// RIGHT:
<img src="photo.jpg" />
<input type="text" />
<br />
Mistake 4: Style as String
// WRONG:
<p style="color: red">Text</p>
// RIGHT:
<p style={{ color: 'red' }}>Text</p>
Mistake 5: Event Handler Invocation
// WRONG: This calls the function immediately during render!
<button onClick={handleClick()}>Click</button>
// handleClick() runs when the component renders, not when clicked
// RIGHT: Pass a function reference
<button onClick={handleClick}>Click</button>
// RIGHT: Wrap in arrow function (when you need to pass arguments)
<button onClick={() => handleClick(id)}>Click</button>
Mistake 6: Rendering Objects
// WRONG: Objects can't be rendered as children
const user = { name: 'Alice', age: 30 };
<p>{user}</p>
// Error: Objects are not valid as a React child
// RIGHT: Access specific properties
<p>{user.name}</p>
<p>{JSON.stringify(user)}</p>
Mistake 7: Missing Key in Lists
// WRONG:
{items.map(item => <li>{item.name}</li>)}
// Warning: Each child in a list should have a unique "key" prop.
// RIGHT:
{items.map(item => <li key={item.id}>{item.name}</li>)}
Mistake 8: Using if Inside JSX
// WRONG:
<div>
{if (isLoggedIn) { return <p>Welcome</p>; }}
</div>
// RIGHT: Use ternary
<div>
{isLoggedIn ? <p>Welcome</p> : null}
</div>
// RIGHT: Use && for simple conditions
<div>
{isLoggedIn && <p>Welcome</p>}
</div>
Mistake 9: Incorrect Map Return
// WRONG: Arrow function with braces but no return
{items.map(item => {
<li key={item.id}>{item.name}</li>
})}
// Returns undefined for each item — nothing renders
// RIGHT: Add return
{items.map(item => {
return <li key={item.id}>{item.name}</li>;
})}
// RIGHT: Use parentheses for implicit return
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
Mistake 10: Adjacent Root Elements
// WRONG:
return (
<h1>Title</h1>
<p>Content</p>
);
// RIGHT:
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
Mistake 11: Confusing = and ==
// WRONG: Assignment in JSX attribute (single =)
<input value="text" /> // This is correct — it's a string
<input value={text} /> // This is correct — it's a variable
// But be careful in expressions:
{count = 5} // WRONG: This is assignment, not comparison
{count === 5 && <p>Five!</p>} // RIGHT: Comparison
Mistake 12: Forgetting Curly Braces for Expressions
// WRONG: String "count" instead of variable count
<p>Count: count</p> // Renders: "Count: count"
<button disabled="false"> // disabled is the STRING "false", which is truthy!
// RIGHT: Use curly braces for JavaScript values
<p>Count: {count}</p> // Renders: "Count: 5"
<button disabled={false}> // disabled is the boolean false
15. JSX Type Checking with TypeScript
TypeScript adds type safety to JSX:
// Typing component props
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
disabled?: boolean;
onClick: () => void;
}
function Button({ label, variant = 'primary', disabled = false, onClick }: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
disabled={disabled}
onClick={onClick}
>
{label}
</button>
);
}
// TypeScript catches errors:
<Button />
// Error: Property 'label' is missing
// Error: Property 'onClick' is missing
<Button label="Submit" onClick={handleSubmit} variant="invalid" />
// Error: Type '"invalid"' is not assignable to type '"primary" | "secondary" | "danger"'
<Button label={42} onClick={handleSubmit} />
// Error: Type 'number' is not assignable to type 'string'
Component Return Types
// React.FC (Function Component) — older pattern, less recommended
const App: React.FC = () => {
return <div>Hello</div>;
};
// Explicit return type — recommended pattern
function App(): JSX.Element {
return <div>Hello</div>;
}
// Or let TypeScript infer the return type (most common):
function App() {
return <div>Hello</div>;
// TypeScript infers the return type as JSX.Element
}
Typing Children
// Accepting children in a component
interface CardProps {
title: string;
children: React.ReactNode; // Most permissive type for children
}
function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
// React.ReactNode accepts:
// - JSX elements (<div>...)
// - Strings ("hello")
// - Numbers (42)
// - Booleans (true/false — renders nothing)
// - null / undefined (renders nothing)
// - Arrays of the above
// - Fragments
16. JSX vs Template Engines
Understanding how JSX differs from traditional template engines:
Handlebars (template engine):
{{#if isLoggedIn}}
<p>Welcome, {{username}}</p>
{{else}}
<p>Please log in</p>
{{/if}}
{{#each items}}
<li>{{this.name}}</li>
{{/each}}
EJS:
<% if (isLoggedIn) { %>
<p>Welcome, <%= username %></p>
<% } else { %>
<p>Please log in</p>
<% } %>
<% items.forEach(item => { %>
<li><%= item.name %></li>
<% }) %>
Pug:
if isLoggedIn
p Welcome, #{username}
else
p Please log in
each item in items
li= item.name
JSX:
{isLoggedIn ? (
<p>Welcome, {username}</p>
) : (
<p>Please log in</p>
)}
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
| Aspect | Template Engines | JSX |
|---|---|---|
| Language | Custom template syntax | Full JavaScript |
| Learning curve | Learn template-specific syntax | Know JavaScript = know JSX |
| Power | Limited to template features | Full JavaScript expressiveness |
| Type safety | No | Yes (with TypeScript) |
| IDE support | Limited | Full (autocomplete, refactoring) |
| Debugging | Template-specific tools | Standard JavaScript debugger |
| Logic | Template helpers, limited | Any JavaScript logic |
| Composition | Partials, layouts | Components (more powerful) |
| Testing | Hard to unit test | Standard JS testing |
The key advantage of JSX: you don't learn a new language. You already know JavaScript. JSX just lets you use that knowledge to describe UI.
17. Key Takeaways
-
JSX is syntactic sugar for
React.createElement(). Every JSX expression compiles to a function call that returns a plain JavaScript object (Virtual DOM node). -
JSX is NOT HTML. Use
className(notclass),htmlFor(notfor), camelCase attributes, object syntax forstyle, and self-closing tags for void elements. -
Curly braces
{}embed JavaScript expressions. Only expressions (values) work inside{}, not statements (if/else, for loops, variable declarations). -
Every JSX expression needs one root element. Use Fragments (
<>...</>) when you don't want an extra DOM wrapper. -
Keys are mandatory in lists. Use stable, unique identifiers (not index, not random). Keys help React's diffing algorithm match elements across renders.
-
JSX automatically prevents XSS. All values are escaped before rendering. Use
dangerouslySetInnerHTMLonly for sanitized, trusted HTML. -
Conditional rendering uses JavaScript patterns:
&&for show/hide, ternary for either/or, early return for loading/error states, and variables for complex conditions. -
The style prop takes a JavaScript object with camelCase property names and string/number values, not a CSS string.
-
false, null, undefined, and true render nothing in JSX. This enables the
{condition && <Element />}pattern. Beware of0— it renders as text. -
TypeScript adds safety to JSX. You can type props, children, event handlers, and refs, catching bugs at compile time instead of runtime.
Explain-It Challenge
-
Write the
React.createElement()equivalent for this JSX, without using any compiler:<section className="hero"> <h1>{title}</h1> {subtitle && <p className="sub">{subtitle}</p>} <button onClick={handleClick}>Get Started</button> </section> -
A colleague writes
{items.length && <List items={items} />}. Whenitemsis an empty array, they see a0on the screen. Explain why and provide three different fixes. -
Explain why this code produces a bug and how to fix it:
<button onClick={deleteItem(item.id)}>Delete</button>
Navigation: ← Project Structure Breakdown · Next → Component-Based Architecture