On this page
JSX and Components: Building Blocks of React
What is JSX?
JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like markup directly inside your JavaScript and TypeScript code. It is React's primary way of describing the UI.
// This is JSX
const element = <h1 className="title">Hello, React!</h1>;
// After compilation by Babel/TypeScript, it becomes:
const element = React.createElement('h1', { className: 'title' }, 'Hello, React!');JSX is purely syntactic sugar — your browser never sees JSX. Your build tool (Vite, TypeScript compiler) transforms it into standard React.createElement() calls. With React 17+ and the new JSX transform, you no longer need to import React manually in every file.
JSX Rules
There are a few rules that distinguish JSX from plain HTML:
1. One Root Element
Every JSX expression must return a single root element. If you need multiple sibling elements, wrap them in a <div> or, better, use a Fragment:
// Wrong — two siblings at the top level
function BadComponent() {
return (
<h1>Title</h1>
<p>Paragraph</p>
);
}
// Correct — using a Fragment
function GoodComponent() {
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
}2. Close All Tags
Every tag must be explicitly closed. Self-closing tags that are valid in HTML must also be self-closed in JSX:
// HTML: <br>, <img>, <input>
// JSX: <br />, <img />, <input />
function Form() {
return (
<form>
<label htmlFor="email">Email</label>
<input type="email" id="email" />
<br />
<button type="submit">Send</button>
</form>
);
}3. camelCase Attributes
JSX attributes follow JavaScript naming conventions:
| HTML | JSX |
|---|---|
class |
className |
for |
htmlFor |
tabindex |
tabIndex |
onclick |
onClick |
onchange |
onChange |
stroke-width |
strokeWidth |
4. JavaScript Expressions in Curly Braces
Use {} to embed any JavaScript expression inside JSX:
const user = { name: 'Alice', score: 1500 };
const isAdmin = true;
function UserBadge() {
return (
<div>
{/* String interpolation */}
<p>Welcome, {user.name}!</p>
{/* Math expression */}
<p>Score: {user.score * 2} (double XP)</p>
{/* Conditional with ternary */}
<span>{isAdmin ? 'Admin' : 'User'}</span>
{/* Function call */}
<p>Joined: {new Date().getFullYear()}</p>
</div>
);
}Functional Components
In modern React (v16.8+), functional components are the standard way to write components. A functional component is just a function that:
- Accepts a single
propsobject as its argument. - Returns JSX (or
nullto render nothing).
// The simplest possible component
function Hello() {
return <p>Hello, world!</p>;
}
// With TypeScript props
interface GreetingProps {
name: string;
role?: string; // optional
}
function Greeting({ name, role = 'User' }: GreetingProps) {
return (
<div>
<h2>{name}</h2>
<small>{role}</small>
</div>
);
}Component Composition
The real power of React comes from composing small components into larger ones. This is how you build complex UIs from simple parts:
function Avatar({ src, alt }: { src: string; alt: string }) {
return <img src={src} alt={alt} className="avatar" width={40} height={40} />;
}
function UserInfo({ name, email }: { name: string; email: string }) {
return (
<div className="user-info">
<strong>{name}</strong>
<small>{email}</small>
</div>
);
}
function UserHeader({ user }: { user: { name: string; email: string; avatar: string } }) {
return (
<header className="user-header">
<Avatar src={user.avatar} alt={`${user.name}'s avatar`} />
<UserInfo name={user.name} email={user.email} />
</header>
);
}Each component is responsible for one thing. Avatar renders an image, UserInfo renders text, UserHeader composes both.
Conditional Rendering
React has no special directive for conditionals — you use plain JavaScript:
interface MessageProps {
status: 'loading' | 'success' | 'error';
text?: string;
}
function Message({ status, text }: MessageProps) {
// Early return
if (status === 'loading') {
return <p>Loading...</p>;
}
return (
<div>
{/* Short-circuit evaluation */}
{status === 'error' && <p className="error">Something went wrong.</p>}
{/* Ternary operator */}
{status === 'success' ? (
<p className="success">{text ?? 'Done!'}</p>
) : null}
</div>
);
}Rendering Lists
Use Array.map() to render lists of elements. Always provide a key prop:
interface Task {
id: string;
title: string;
done: boolean;
}
const tasks: Task[] = [
{ id: 'a1', title: 'Learn JSX', done: true },
{ id: 'b2', title: 'Build a component', done: false },
{ id: 'c3', title: 'Ship to production', done: false },
];
function TaskList() {
return (
<ul>
{tasks.map((task) => (
<li
key={task.id}
style={{ textDecoration: task.done ? 'line-through' : 'none' }}
>
{task.title}
</li>
))}
</ul>
);
}Filtering and Transforming Lists
You can chain array methods before rendering:
function DoneTaskList() {
const doneTasks = tasks.filter((t) => t.done);
return (
<section>
<h3>Completed ({doneTasks.length})</h3>
<ul>
{doneTasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
</section>
);
}Inline Styles
For dynamic styles in JSX, pass a JavaScript object to the style prop. Property names use camelCase:
function ProgressBar({ value }: { value: number }) {
return (
<div className="progress-track">
<div
className="progress-fill"
style={{
width: `${value}%`,
backgroundColor: value >= 100 ? '#22c55e' : '#3b82f6',
transition: 'width 0.3s ease',
}}
role="progressbar"
aria-valuenow={value}
aria-valuemin={0}
aria-valuemax={100}
/>
</div>
);
}Naming Conventions
React components must start with a capital letter. This is how React distinguishes between custom components and native HTML elements:
// React sees <div> as the HTML element
const a = <div />;
// React sees <Div> as a component — it will look for a Div function
const b = <Div />; // TS error if Div is not definedKeep component files named after the component: ProductCard.tsx → function ProductCard.
Exporting Components
You can use named or default exports. Prefer named exports for better refactoring support:
// Named export (preferred in most codebases)
export function Button({ label }: { label: string }) {
return <button type="button">{label}</button>;
}
// Default export (common for page-level components)
export default function HomePage() {
return <main><h1>Home</h1></main>;
}In the next lesson, you will explore props and children in depth, including how to build flexible, composable components with TypeScript.
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
tags: string[];
}
interface ProductCardProps {
product: Product;
onAddToCart: (id: number) => void;
}
function ProductCard({ product, onAddToCart }: ProductCardProps) {
return (
<article className="product-card">
<h2>{product.name}</h2>
<p className="price">${product.price.toFixed(2)}</p>
{/* Conditional rendering */}
{product.inStock ? (
<span className="badge badge--green">In stock</span>
) : (
<span className="badge badge--red">Out of stock</span>
)}
{/* List rendering */}
<ul className="tags">
{product.tags.map((tag) => (
<li key={tag}>{tag}</li>
))}
</ul>
<button
type="button"
disabled={!product.inStock}
onClick={() => onAddToCart(product.id)}
>
Add to cart
</button>
</article>
);
}
export default ProductCard;
Sign in to track your progress