JSX Deep Dive
Now that you understand JSX basics, let's explore advanced patterns, the compilation process, and best practices for writing clean, maintainable JSX.
How JSX Compiles
JSX is syntactic sugar for React.createElement() calls. Modern React (17+) uses an automatic JSX transform:
jsx1// What you write2function Greeting() {3 return <h1 className="title">Hello!</h1>;4}56// What it becomes (automatic transform)7import { jsx as _jsx } from 'react/jsx-runtime';89function Greeting() {10 return _jsx('h1', { className: 'title', children: 'Hello!' });11}
The automatic transform means:
- No need to
import Reactin every file - Slightly smaller bundle size
- Better future optimizations
JSX Element Types
JSX can render different types of elements:
jsx1// 1. HTML elements (lowercase)2<div>Native HTML</div>34// 2. Components (PascalCase)5<MyComponent />67// 3. Dynamic components8const Component = isAdmin ? AdminPanel : UserPanel;9<Component />1011// 4. Namespaced components12<Form.Input />13<Form.Button />
Naming convention matters! React uses the case to distinguish:
<button>→ HTML element<Button>→ React component
Spread Attributes
Pass all props at once using spread:
jsx1const buttonProps = {2 className: 'btn btn-primary',3 disabled: false,4 onClick: handleClick,5};67// Spread all props8<button {...buttonProps}>Click me</button>910// Override specific props11<button {...buttonProps} disabled={true}>12 Disabled13</button>1415// Order matters! Later props override earlier ones16<button disabled={true} {...buttonProps}>17 Actually not disabled (buttonProps.disabled = false wins)18</button>
Rest Props Pattern
Accept and forward unknown props:
jsx1function Button({ variant, children, ...rest }) {2 const className = `btn btn-${variant}`;34 // Forward all other props to the native button5 return (6 <button className={className} {...rest}>7 {children}8 </button>9 );10}1112// Usage - onClick, disabled, etc. are forwarded13<Button variant="primary" onClick={save} disabled={loading}>14 Save15</Button>
Boolean Attributes
In JSX, attributes without values are truthy:
jsx1// These are equivalent2<input disabled={true} />3<input disabled />45// These are equivalent6<input disabled={false} />7<input /> // Just omit the attribute
Common boolean attributes:
disabledcheckedreadOnlyrequiredautoFocus
Inline Styles
JSX styles use objects with camelCase properties:
jsx1function Box() {2 const style = {3 backgroundColor: 'blue', // not background-color4 fontSize: '16px', // not font-size5 padding: 20, // numbers assume 'px'6 marginTop: '1rem',7 };89 return <div style={style}>Styled box</div>;10}1112// Inline object (double braces)13<div style={{ color: 'red', fontWeight: 'bold' }}>14 Red bold text15</div>
Note: Inline styles have higher specificity but are harder to maintain. Prefer CSS classes for most styling.
Dangerous HTML
To render raw HTML (use sparingly!):
jsx1function Article({ htmlContent }) {2 // WARNING: Only use with trusted content!3 return (4 <div dangerouslySetInnerHTML={{ __html: htmlContent }} />5 );6}
This bypasses React's XSS protection. Only use when:
- Content comes from a trusted source
- You've sanitized the HTML
- There's no other option (like markdown parsers)
Comments in JSX
Use JavaScript comments inside curly braces:
jsx1function Component() {2 return (3 <div>4 {/* This is a JSX comment */}5 <h1>Title</h1>67 {/*8 Multi-line comments9 work like this10 */}11 <p>Content</p>12 </div>13 );14}
Whitespace in JSX
JSX removes whitespace differently than HTML:
jsx1// These render the same (no space between spans)2<div><span>A</span><span>B</span></div>3<div>4 <span>A</span>5 <span>B</span>6</div>78// To add space, use explicit space or CSS9<div>10 <span>A</span>{' '}<span>B</span>11</div>
Expressions vs Statements
JSX only accepts expressions (things that return a value), not statements:
jsx1// EXPRESSIONS work2{isAdmin && <AdminPanel />} // logical3{count > 0 ? count : 'None'} // ternary4{items.map(i => <Item key={i.id} />)} // function call5{(() => { return 'IIFE'; })()} // IIFE67// STATEMENTS don't work8{if (isAdmin) { return <Admin />; }} // ERROR!9{for (let i of items) { ... }} // ERROR!10{let x = 5;} // ERROR!
For complex logic, compute before the return:
jsx1function Dashboard({ user, items }) {2 // Do complex logic here3 let content;4 if (!user) {5 content = <Login />;6 } else if (items.length === 0) {7 content = <EmptyState />;8 } else {9 content = <ItemList items={items} />;10 }1112 // Return is clean13 return <div className="dashboard">{content}</div>;14}
Null, Undefined, and Booleans
These values render nothing in JSX:
jsx1<div>2 {null} {/* renders nothing */}3 {undefined} {/* renders nothing */}4 {true} {/* renders nothing */}5 {false} {/* renders nothing */}6</div>78// Useful for conditional rendering9<div>10 {showBanner && <Banner />}11 {user ?? <Login />}12</div>
Watch out for falsy values that DO render:
jsx1// These render as text!2<div>{0}</div> {/* renders "0" */}3<div>{''}</div> {/* renders nothing (empty string is special) */}4<div>{NaN}</div> {/* renders "NaN" */}56// Common bug with && operator7{items.length && <List items={items} />} // Shows "0" if empty!89// Fix: explicitly check boolean10{items.length > 0 && <List items={items} />}
JSX Type Checking
TypeScript or PropTypes help catch JSX errors:
tsx1// TypeScript2interface ButtonProps {3 variant: 'primary' | 'secondary';4 children: React.ReactNode;5 onClick?: () => void;6}78function Button({ variant, children, onClick }: ButtonProps) {9 return (10 <button className={`btn-${variant}`} onClick={onClick}>11 {children}12 </button>13 );14}1516// Usage - TypeScript catches errors17<Button variant="tertiary">Save</Button> // Error: invalid variant
jsx1// PropTypes (runtime checking)2import PropTypes from 'prop-types';34function Button({ variant, children, onClick }) {5 return (6 <button className={`btn-${variant}`} onClick={onClick}>7 {children}8 </button>9 );10}1112Button.propTypes = {13 variant: PropTypes.oneOf(['primary', 'secondary']).isRequired,14 children: PropTypes.node.isRequired,15 onClick: PropTypes.func,16};
JSX Best Practices
1. Extract Complex JSX
jsx1// Before: hard to read2function Dashboard() {3 return (4 <div>5 {user ? (6 <div className="user-panel">7 <img src={user.avatar} alt={user.name} />8 <h2>{user.name}</h2>9 <p>{user.bio}</p>10 <button onClick={logout}>Logout</button>11 </div>12 ) : (13 <div className="login-panel">14 <h2>Welcome</h2>15 <button onClick={login}>Login</button>16 </div>17 )}18 </div>19 );20}2122// After: extract components23function UserPanel({ user, onLogout }) {24 return (25 <div className="user-panel">26 <img src={user.avatar} alt={user.name} />27 <h2>{user.name}</h2>28 <p>{user.bio}</p>29 <button onClick={onLogout}>Logout</button>30 </div>31 );32}3334function LoginPanel({ onLogin }) {35 return (36 <div className="login-panel">37 <h2>Welcome</h2>38 <button onClick={onLogin}>Login</button>39 </div>40 );41}4243function Dashboard() {44 return (45 <div>46 {user ? (47 <UserPanel user={user} onLogout={logout} />48 ) : (49 <LoginPanel onLogin={login} />50 )}51 </div>52 );53}
2. Use Semantic HTML
jsx1// Avoid2<div className="header">3 <div className="nav">...</div>4</div>56// Prefer7<header>8 <nav>...</nav>9</header>
3. Keep Returns Clean
jsx1// Compute before return2function ProductCard({ product }) {3 const formattedPrice = new Intl.NumberFormat('en-US', {4 style: 'currency',5 currency: 'USD',6 }).format(product.price);78 const stockStatus = product.stock > 0 ? 'In Stock' : 'Out of Stock';9 const stockClass = product.stock > 0 ? 'stock-good' : 'stock-bad';1011 return (12 <article className="product-card">13 <h2>{product.name}</h2>14 <p className="price">{formattedPrice}</p>15 <p className={stockClass}>{stockStatus}</p>16 </article>17 );18}
Summary
Key JSX patterns:
- Spread attributes:
{...props} - Rest props:
({ known, ...rest }) - Boolean attributes:
disabledequalsdisabled={true} - Inline styles: camelCase objects
- Only expressions in
{} null,undefined, booleans render nothing- Watch for
0rendering with&&
Next, we'll put this knowledge into practice by building a profile card component!