React Cheat Sheet
In this React cheat sheet, you'll find every hook, pattern, and API. Searchable, filterable by React version, one-click copy.
| Hook / Feature | Min Version | Notes |
|---|---|---|
| useState, useEffect, useContext, useRef, useCallback, useMemo, useReducer | 16.8 | All original hooks |
| useId | 18 | Stable IDs for a11y, SSR-safe |
| useTransition | 18 | Concurrent mode |
| useDeferredValue | 18 | Concurrent mode |
| useSyncExternalStore | 18 | External store integration |
| useInsertionEffect | 18 | CSS-in-JS library authors |
| useActionState | 19 | Replaces useFormState |
| useOptimistic | 19 | Optimistic UI updates |
| use() | 19 | Read Promise/context in render |
| ref as prop (no forwardRef) | 19 | Simplifies ref forwarding |
| Context without .Provider | 19 | Render Context directly |
| Scenario | Best choice |
|---|---|
| Theme, locale, current user | Context |
| Complex forms with related fields | useReducer + Context |
| Shopping cart, auth, app-wide state | Zustand |
| Frequently updating shared values | Zustand (slice selectors) |
| Independent atomic state pieces | Jotai |
| Server state (fetch, cache, sync) | TanStack Query |
What is React?
React is a JavaScript library for building user interfaces through a component-based architecture, maintained by Meta (formerly Facebook) since its open-source release in 2013.
It uses a declarative rendering model: you describe what the UI should look like at any given state, and React handles the actual DOM updates.
According to the Stack Overflow Developer Survey 2024, 39.5% of professional developers use React, making it the most-used web framework after Node.js (Statista, 2024).
React runs on over 11 million websites globally and holds a 42.62% market share among JavaScript frameworks (Stack Overflow Survey 2024).
React vs ReactDOM
These are 2 separate packages with distinct roles.
React: core library that handles component creation, JSX compilation, and the reconciliation algorithm.
ReactDOM: the renderer that connects React's virtual DOM to the actual browser DOM via ReactDOM.createRoot().
The split matters in cross-platform app development contexts. React Native, for instance, replaces ReactDOM with its own native renderer, while the React core stays the same.
Virtual DOM and Reconciliation
React never updates the real DOM directly.
Instead, it builds a virtual DOM (a lightweight in-memory copy of the DOM tree), diffs the new version against the previous one, and patches only what changed. This process is called reconciliation.
React's reconciliation algorithm uses a heuristic O(n) diffing approach. Without it, comparing two DOM trees would require O(n³) operations, making updates 10x-100x more expensive at scale (React documentation, 2024).
React 18 and React 19
React 18 introduced concurrent rendering, automatic batching, and the createRoot API.
React 19, released in late 2024, added the React Compiler (formerly React Forget), which handles automatic memoization without manual useMemo and useCallback calls. Nearly 48.4% of daily React users migrated to React 19 within months of release (State of React 2025).
|
Version |
Key Feature |
Impact |
|---|---|---|
|
React 16 |
Hooks introduced |
Replaced class components for state logic |
|
React 18 |
Concurrent rendering, |
Non-blocking UI updates |
|
React 19 |
React Compiler, |
Automatic memoization |
What Does the React Component Model Cover?
The React component model defines every visible piece of UI as a self-contained, reusable unit that manages its own logic and output.
React's Component Architecture produces 60% faster development times compared to monolith architecture (eSpark Info, 2024).
Functional Component Syntax
Functional components are the current standard. A functional component is a JavaScript function that accepts a props object and returns JSX.
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
Key rules:
-
Component names must start with a capital letter (PascalCase)
-
Must return a single root element (or a Fragment)
-
No
thiskeyword, no lifecycle methods directly
React.memo wraps a functional component to skip re-renders when props haven't changed. It's a shallow comparison by default.
Class Component Syntax
Class components still appear in production codebases built before React 16.8, so you'll run into them.
Structure:
-
Extend
React.Component -
Define a
render()method that returns JSX -
State lives in
this.state, updated viathis.setState()
class Counter extends React.Component {
state = { count: 0 };
render() {
return <button onClick={() => this.setState({ count: this.state.count + 1 })}>
{this.state.count}
</button>;
}
}
PureComponent is the class-based equivalent of React.memo. It implements shouldComponentUpdate with a shallow prop and state comparison.
Worth knowing for legacy codebase audits and gradual migrations. Functional components are preferred for all new code.
Component Composition
React builds UIs by nesting components. The children prop lets a parent component render whatever JSX is passed between its opening and closing tags.
function Card({ children }) {
return <div className="card">{children}</div>;
}
// Usage
<Card><p>Content goes here</p></Card>
This pattern is the foundation of most React component patterns used in production apps.
What is JSX and How Does It Work?
JSX is syntactic sugar that lets you write HTML-like syntax inside JavaScript files. Babel compiles every JSX expression into React.createElement() calls at build time.
// JSX
const el = <h2 className="title">Hello</h2>;
// Compiled output
const el = React.createElement("h2", { className: "title" }, "Hello");
JSX Syntax Rules
4 rules trip up developers most often:
-
classNamenotclass—classis a reserved JavaScript keyword -
htmlFornotfor— same reason,forconflicts with JavaScript's loop syntax -
Self-closing tags required —
<img />,<input />,<br />must close -
Single root element — wrap multiple siblings in a
<div>or<React.Fragment>(shorthand:<>)
React 19 mandates the new JSX transform, which no longer requires import React from 'react' at the top of every file. The compiler handles it automatically.
JavaScript Expressions in JSX
Curly braces {} let you embed any valid JavaScript expression inside JSX.
const user = { name: "Alex", score: 98 };
return (
<div>
<p>{user.name}</p>
<p>Score: {user.score > 90 ? "Excellent" : "Good"}</p>
</div>
);
Objects and arrays won't render directly, which is a common source of errors for beginners.
Conditional Rendering in JSX
3 patterns, each suited to different situations:
Ternary: renders one of two elements based on a condition.
{isLoggedIn ? <Dashboard /> : <Login />}
Short-circuit (&&): renders an element only when a condition is true. Watch out for 0 — it renders as the number zero, not nothing.
{count > 0 && <Badge count={count} />}
Early return: cleanest option when the falsy case renders nothing at all.
if (!data) return null;
return <DataView data={data} />;
How Does Props Work in React?
Props (short for properties) are the mechanism React uses to pass data from a parent component to a child. Props are read-only. A child component cannot modify its own props.
Passing and Destructuring Props
Any JavaScript value can be passed as a prop: strings, numbers, booleans, arrays, objects, and functions.
<UserCard
name="Alex"
age={28}
isAdmin={true}
scores={[88, 92, 79]}
onDelete={() => handleDelete(id)}
/>
Destructuring in function parameters keeps components readable:
function UserCard({ name, age, isAdmin, onDelete }) {
return <div onClick={onDelete}>{name}, {age}</div>;
}
Default Props and PropTypes
Default values prevent undefined errors when a prop isn't passed:
function Button({ label = "Click me", size = "md" }) {
return <button className={`btn btn-${size}`}>{label}</button>;
}
PropTypes are runtime type checks from the separate prop-types package. Useful for catching bugs during development, though most teams now use TypeScript for compile-time checks. Over 80% of React developers have adopted TypeScript in their projects (State of JavaScript 2022).
The children Prop
children is a special prop that holds whatever is passed between a component's opening and closing tags.
It's what makes wrapper components possible, from UI cards to layout containers to modal windows. React.Children.map and React.Children.count are utility methods for programmatically working with the children prop when you need more control.
How Does State Work in React?
State is data that belongs to a component and changes over time. When state updates, React re-renders the component and its children.
useState Hook Syntax
useState returns a pair: the current value and a setter function.
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: "", email: "" });
3 things developers get wrong with useState:
-
Mutating state directly (
state.count++) instead of calling the setter -
Not using functional updates when new state depends on previous state (
setCount(prev => prev + 1)) -
Storing derived data in state instead of computing it during render
State updates in React 18 are automatically batched, meaning multiple setState calls inside the same event handler trigger only one re-render.
Lifting State Up Pattern
When 2 sibling components need to share state, move that state to their closest common parent.
function Parent() {
const [value, setValue] = useState("");
return (
<>
<Input value={value} onChange={setValue} />
<Preview value={value} />
</>
);
}
This is the core pattern for managing shared data in React without reaching for a state library. Meta uses this pattern extensively across Facebook's UI components.
Object and Array State
Never mutate state objects or arrays. Create new references instead.
// Updating an object
setUser(prev => ({ ...prev, name: "Alex" }));
// Adding to an array
setItems(prev => [...prev, newItem]);
// Removing from an array
setItems(prev => prev.filter(item => item.id !== targetId));
Mutation bypasses React's change detection. The component won't re-render because the reference hasn't changed, even though the data has.
What Are React Hooks and Which Are Built-In?
React Hooks, introduced in React 16.8, let functional components access state, lifecycle behavior, and context without class components. According to the State of React 2025, useEffect is used by 98% of React developers, though it also has the lowest satisfaction ratio of any built-in hook.
There are 2 rules that always apply: call hooks only at the top level (not inside loops or conditions) and only inside React functions (not regular JavaScript functions).
|
Hook |
Purpose |
Common Use Case |
|---|---|---|
|
|
Local component state |
Form inputs, toggles, counters |
|
|
Side effects |
API calls, subscriptions, timers |
|
|
Consume context |
Theme, auth, locale |
|
|
DOM refs and persistent values |
Input focus, animation, previous value |
|
|
Complex state logic |
Multi-step forms, shopping carts |
|
|
Memoize computed values |
Expensive calculations, filtered lists |
|
|
Memoize functions |
Prevent child re-renders |
useEffect Dependency Array Patterns
useEffect takes 2 arguments: a callback function and a dependency array. The dependency array controls when the effect runs.
3 dependency array behaviors:
useEffect(() => { /* runs after every render */ });
useEffect(() => { /* runs once on mount */ }, []);
useEffect(() => { /* runs when userId changes */ }, [userId]);
The cleanup function (returned from the effect) runs before the next effect and on unmount:
useEffect(() => {
const timer = setInterval(() => tick(), 1000);
return () => clearInterval(timer); // cleanup
}, []);
useEffect complaints account for 37% of React developer frustrations in the State of React 2025 survey, mostly around stale closures and dependency array management.
useReducer vs useState
useReducer fits better than useState when state transitions involve multiple sub-values or when the next state depends on a specific action type.
Choose useState when:
-
State is a simple value (boolean, string, number)
-
Updates are direct replacements
Choose useReducer when:
-
State has 3+ related fields
-
Multiple actions can modify the same state
-
State logic needs to be testable in isolation
function reducer(state, action) {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
default: return state;
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
Redux's core architecture is this exact pattern at a global scale, which is why React context vs Redux comparisons always come back to useReducer as the underlying model.
useMemo and useCallback
Both hooks prevent unnecessary recalculations and re-renders. Both take a dependency array.
useMemo caches a value. useCallback caches a function reference.
const filteredList = useMemo(
() => items.filter(i => i.active),
[items]
);
const handleSubmit = useCallback(
() => onSubmit(formData),
[formData, onSubmit]
);
Don't overuse them. Every useMemo and useCallback call has overhead. Apply them when profiling shows actual performance issues, or when passing stable function references to memoized child components. The React 19 Compiler automates most of this.
useRef Hook
useRef returns a mutable ref object whose .current property persists across renders without triggering a re-render.
2 distinct use cases:
// 1. DOM reference
const inputRef = useRef(null);
<input ref={inputRef} />
// inputRef.current.focus()
// 2. Persist a value across renders
const renderCount = useRef(0);
renderCount.current += 1; // won't cause a re-render
How Does the React Component Lifecycle Work?
Every React component goes through 3 phases: Mounting (added to the DOM), Updating (re-rendered due to state or prop changes), and Unmounting (removed from the DOM).
The app lifecycle concept in React maps directly to these 3 phases, each with corresponding hook equivalents for functional components.
Lifecycle to Hook Mapping Table
|
Phase |
Class Method |
Hook Equivalent |
|---|---|---|
|
Mount |
|
|
|
Update |
|
|
|
Unmount |
|
|
|
Render optimization |
|
|
|
Before paint |
|
|
Mounting Phase
When a component mounts, React runs the render, commits the output to the DOM, and then fires the useEffect with an empty dependency array.
useEffect(() => {
// runs once after first render
fetchUserData(userId);
}, []);
This is where you initiate API integration calls, set up subscriptions, or attach event listeners.
Updating Phase
A component updates when its state changes, its props change, or its parent re-renders.
useEffect(() => {
document.title = `User: ${userId}`;
}, [userId]); // runs whenever userId changes
useLayoutEffect vs useEffect:
useLayoutEffect fires synchronously after DOM mutations but before the browser paints. Use it for DOM measurements (reading scroll position, element dimensions) where you need to act before the user sees the frame.
useEffect fires asynchronously after paint. Use it for everything else.
Unmounting Phase
The cleanup function returned from useEffect runs when the component unmounts.
useEffect(() => {
const subscription = api.subscribe(onData);
return () => subscription.unsubscribe(); // cleanup on unmount
}, []);
Forgetting cleanups is one of the most common sources of memory leaks in React applications. Every subscription, timer, or event listener added in an effect needs a corresponding cleanup.
getDerivedStateFromProps and getSnapshotBeforeUpdate exist in class components for edge cases where state needs to sync with prop changes during the render phase. In functional components, this is typically handled with useMemo or controlled state.
How Does Context API Work in React?
Context API solves prop drilling: the problem of passing data through multiple intermediate components that don't use the data themselves.
React Context is not a replacement for state management libraries. Mark Erikson, Redux maintainer, notes that Context lacks structured updates, middleware, and devtools — all essential for complex state (Patterns.dev, 2025).
|
Step |
API |
Purpose |
|---|---|---|
|
Create |
|
Creates the context object |
|
Provide |
|
Makes value available to the tree |
|
Consume |
|
Reads the current context value |
Creating and Providing Context
const ThemeContext = React.createContext("light");
function App() {
const [theme, setTheme] = useState("dark");
return (
<ThemeContext.Provider value={theme}>
<Layout />
</ThemeContext.Provider>
);
}
The defaultValue in createContext() only applies when a component has no matching Provider above it in the tree.
Every consumer re-renders when the Provider's value prop changes. For large trees, this can be a bottleneck.
Consuming Context
useContext replaces both Context.Consumer and the legacy contextType pattern.
function ThemeButton() {
const theme = useContext(ThemeContext);
return <button className={`btn-${theme}`}>Click</button>;
}
Performance note: split context by update frequency.
Put rarely-changing values (user auth, locale) in one context. Put frequently-changing values (notifications, cart count) in a separate context. This limits how many components re-render on each update.
Netflix uses a similar pattern in their React-based UI, separating user preferences context from real-time playback state to avoid full-tree re-renders on every interaction.
When Context Is Appropriate vs State Managers
Use Context for:
-
Theme (light/dark mode)
-
Current authenticated user
-
App-wide locale or language
Use Redux, Zustand, or Jotai for:
-
Frequent updates across many components
-
Complex state transitions with actions
-
Middleware needs (logging, persistence, undo/redo)
How Does React Handle Events?
React uses a synthetic event system that wraps native browser events in a cross-browser compatible interface. All event listeners are attached to the root DOM node, not individual elements.
Synthetic Events and Handler Syntax
React event names use camelCase: onClick, onChange, onSubmit, onKeyDown.
function Button() {
function handleClick(event) {
event.preventDefault();
console.log("clicked");
}
return <button onClick={handleClick}>Click</button>;
}
Never call the function directly in JSX (onClick={handleClick()}). That executes on render, not on click.
Passing Arguments to Event Handlers
Use an arrow function wrapper to pass extra arguments without triggering immediate execution.
<button onClick={() => handleDelete(item.id)}>Delete</button>
event.stopPropagation() prevents the event from bubbling up to parent elements.
event.preventDefault() blocks the default browser behavior (form submission, link navigation).
Controlled vs Uncontrolled Components
Controlled: React state drives the input value. Every keystroke updates state and re-renders.
const [email, setEmail] = useState("");
<input value={email} onChange={e => setEmail(e.target.value)} />
Uncontrolled: DOM manages its own value. React reads it via useRef when needed.
const inputRef = useRef();
<input ref={inputRef} defaultValue="initial" />
// inputRef.current.value on submit
Controlled is the default recommendation. Uncontrolled fits file inputs and cases where you're integrating React with a non-React front-end development codebase.
What Are React Lists, Keys, and Conditional Rendering Patterns?
React renders lists through Array.map(), transforming data arrays into JSX element arrays. The key prop is not optional for list items.
Rendering Lists with map()
const users = [
{ id: 1, name: "Alex" },
{ id: 2, name: "Sam" },
];
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
Keys must be stable, unique among siblings, and consistent across renders.
Using array index as a key (key={index}) breaks if items reorder or get removed. React compares keys to decide which items changed. A wrong key means wrong DOM updates.
Key Prop Rules
3 sources for valid keys, in order of preference:
-
Database IDs — already unique and stable
-
Generated UUIDs — stable if assigned once, not on every render
-
Array index — only acceptable for static, non-reordering lists
Airbnb ran into key-related rendering bugs at scale when using indexes for dynamic listing cards. Switching to stable property IDs eliminated the issue.
Conditional Rendering Patterns
The && operator has one well-known trap: if the left side evaluates to 0, React renders the number zero, not nothing.
// Bug: renders "0" when count is 0
{count && <Badge count={count} />}
// Fix: explicit boolean
{count > 0 && <Badge count={count} />}
Ternary is safer for binary conditions. Early return null is cleanest for components that should render nothing at all.
How Does React Router Work?
React Router v6 is the standard for client-side routing in React single-page applications. With 3.84 billion lifetime npm downloads and 11 million dependent repositories, it's one of the most downloaded JavaScript libraries in history (GitHub, 2025).
Core Components and Hooks
|
API |
Purpose |
|---|---|
|
|
Wraps the app, provides routing context |
|
|
Define URL-to-component mappings |
|
|
Client-side navigation (no page reload) |
|
|
Programmatic navigation |
|
|
Read dynamic URL parameters |
|
|
Renders matched child routes |
Basic Route Setup
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
path="*" catches all unmatched routes. Always include it for 404 handling.
The React router tutorial covers nested routes, loaders, and data fetching patterns introduced in v6.4+.
Nested Routes and Outlet
Nested routes let child components render inside a parent layout.
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Overview />} />
<Route path="settings" element={<Settings />} />
</Route>
<Outlet /> inside DashboardLayout renders whichever child route matches. This keeps shared layout (nav, sidebar) consistent across sub-pages without duplication.
Programmatic Navigation
useNavigate replaces the v5 useHistory hook.
const navigate = useNavigate();
// Navigate forward
navigate("/dashboard");
// Navigate back
navigate(-1);
// Navigate with state
navigate("/checkout", { state: { fromCart: true } });
NavLink automatically applies an active class when its route matches, useful for navigation menus without custom active-state logic.
How Does React Handle Forms?
React gives you 2 options for forms: controlled components (React drives the value) and uncontrolled components (the DOM drives the value). Most production apps use controlled inputs for validation accuracy.
Controlled Form Pattern
Every input has a value prop tied to state and an onChange handler.
function LoginForm() {
const [form, setForm] = useState({ email: "", password: "" });
function handleChange(e) {
setForm(prev => ({ ...prev, [e.target.name]: e.target.value }));
}
function handleSubmit(e) {
e.preventDefault();
// submit logic
}
return (
<form onSubmit={handleSubmit}>
<input name="email" value={form.email} onChange={handleChange} />
<input name="password" type="password" value={form.password} onChange={handleChange} />
<button type="submit">Login</button>
</form>
);
}
e.preventDefault() stops the browser from reloading the page on submit.
textarea and select Syntax
Both follow the controlled pattern but with one difference from HTML.
<textarea value={bio} onChange={e => setBio(e.target.value)} />
<select value={role} onChange={e => setRole(e.target.value)}>
<option value="admin">Admin</option>
<option value="viewer">Viewer</option>
</select>
HTML <select> uses selected on the <option>. React uses value on the <select> itself.
React Hook Form vs Native State
As of April 2024, React Hook Form receives 4.9 million weekly npm downloads versus Formik's 2.5 million (npm trends, 2024).
React Hook Form advantages:
-
Uncontrolled inputs by default (fewer re-renders)
-
27.9 kB bundle size vs Formik's 44.7 kB
-
Actively maintained; Formik's own docs discourage it for new projects
import { useForm } from "react-hook-form";
function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register("email")} />
<button type="submit">Submit</button>
</form>
);
}
For simple forms, native React state works fine. React Hook Form earns its place in forms with 5+ fields, complex validation, or performance-sensitive interactions.
What Are React Performance Optimization Techniques?
The average React app in 2026 ships 1.5–3 MB of JavaScript on initial load, which takes 15–30 seconds on a 3G connection (jsmanifest, 2026). Code splitting and lazy loading cut that significantly.
Combining lazy loading and code splitting can achieve a 40% reduction in page load time (ResearchGate, 2024).
React.memo and Memoization
React.memo skips re-rendering a component when its props haven't changed (shallow comparison).
const UserCard = React.memo(function UserCard({ name, score }) {
return <div>{name}: {score}</div>;
});
When it helps: components that receive the same props frequently while siblings update.
When it doesn't: components that almost always receive different props. The comparison overhead outweighs the render savings.
Code Splitting with React.lazy
React.lazy loads a component only when it's first rendered.
const Dashboard = React.lazy(() => import("./Dashboard"));
function App() {
return (
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
);
}
Lazy loading components reduces initial bundle size by 20–70%, depending on application complexity (Growin, 2025).
Suspense wraps lazy components and renders the fallback UI while the module loads. Always keep Suspense close to the lazy component, not at the app root.
List Virtualization
The problem: rendering 10,000 list items creates 10,000 DOM nodes. Even if only 10 are visible.
The fix: react-window or react-virtual render only visible rows, recycling DOM nodes as the user scrolls.
import { FixedSizeList } from "react-window";
<FixedSizeList height={400} itemCount={10000} itemSize={35}>
{({ index, style }) => <div style={style}>{items[index].name}</div>}
</FixedSizeList>
Twitter's feed, Airbnb's search results, and Notion's document lists all use this pattern to handle large datasets without performance issues.
Common Performance Anti-Patterns
Avoid these in JSX:
-
Anonymous functions as props (
onClick={() => fn()}on every render creates a new function reference, breakingReact.memo) -
Object literals as props (
style={{ color: "red" }}creates a new object every render) -
Context with too many consumers (one value change re-renders every consumer)
The React DevTools Profiler shows which components re-render most. Always profile before optimizing.
What Are Common React Patterns Used in Production?
These 5 patterns appear consistently across production React codebases. Custom hooks replaced HOCs and render props for 90% of logic-sharing use cases (TurboDocx, 2026).
Custom Hook Pattern
A custom hook is a function starting with use that calls built-in hooks internally.
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handler = () =>
setSize({ width: window.innerWidth, height: window.innerHeight });
window.addEventListener("resize", handler);
return () => window.removeEventListener("resize", handler);
}, []);
return size;
}
Why it works: logic lives in one place, reusable across any component, testable in isolation without mounting a component tree.
Custom hooks are the primary way to share stateful logic in React today. Libraries like React Hook Form, TanStack Query, and Zustand all expose their API through custom hooks.
Compound Component Pattern
Compound components share implicit state between a parent and its children via Context.
function Tabs({ children }) {
const [active, setActive] = useState(0);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
}
Tabs.Tab = function Tab({ index, children }) {
const { active, setActive } = useContext(TabsContext);
return (
<button
className={active === index ? "active" : ""}
onClick={() => setActive(index)}
>
{children}
</button>
);
};
// Usage
<Tabs>
<Tabs.Tab index={0}>Overview</Tabs.Tab>
<Tabs.Tab index={1}>Settings</Tabs.Tab>
</Tabs>
Radix UI, Headless UI, and Reach UI all build their component APIs on this pattern. The best UI libraries for React use compound components to give consumers full control over rendering while handling accessibility internally.
Higher-Order Components
A Higher-Order Component (HOC) is a function that takes a component and returns a new, enhanced component.
function withAuth(Component) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <Component {...props} user={user} />;
};
}
HOCs still appear in codebases for cross-cutting concerns like authentication, logging, and error boundaries, where wrapping at the component level makes more sense than a custom hook.
The React with TypeScript guide covers how to properly type HOC generics to avoid prop inference issues.
Render Props Pattern
A render prop is a function passed as a prop that a component calls to determine what to render.
<DataFetcher url="/api/users">
{({ data, loading }) =>
loading ? <Spinner /> : <UserList users={data} />
}
</DataFetcher>
Render props remain useful when a wrapper component needs to be part of the JSX tree (not just supply data). Libraries like Downshift, react-beautiful-dnd, and Framer Motion's <AnimatePresence> use render props for exactly this reason (Patterns.dev, 2025).
For pure logic sharing, custom hooks are cleaner. Render props win when the wrapper needs to own the subtree.
Container/Presentational Pattern
Container components: handle data fetching, state, and business logic. No styling or layout.
Presentational components: receive data via props, render UI. No direct API calls or state logic.
// Container
function UserListContainer() {
const [users, setUsers] = useState([]);
useEffect(() => { fetchUsers().then(setUsers); }, []);
return <UserList users={users} />;
}
// Presentational
function UserList({ users }) {
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
The split makes presentational components easy to test and easy to reuse with different data sources. It also maps cleanly to how React testing libraries separate unit tests (presentational, no mocks) from integration tests (container, mocked API).
FAQ on React
What is the difference between props and state in React?
Props are read-only values passed from parent to child. State is data a component owns and can change over time. When state updates, React re-renders the component. Props flow down; state stays local unless lifted up.
When should I use useReducer instead of useState?
Use useReducer when state has 3 or more related fields, or when multiple actions modify the same data. useState works fine for simple values. For anything resembling a mini state machine, useReducer keeps logic cleaner and easier to test.
What are React hooks rules?
2 rules, no exceptions: call hooks only at the top level of a function (never inside loops or conditions), and only inside React functional components or custom hooks. Breaking either rule causes unpredictable behavior across renders.
What is JSX and do I need it?
JSX is syntactic sugar that compiles to React.createElement() calls via Babel. You don't technically need it, but writing React without it is painful. Every JSX tag becomes a function call, so JSX is just a more readable way to describe your UI tree.
How does the React virtual DOM work?
React maintains a lightweight in-memory copy of the DOM. On state changes, it builds a new virtual DOM tree, diffs it against the previous one, and applies only the minimal set of real DOM updates needed. This reconciliation process avoids costly full-page re-renders.
What is the difference between controlled and uncontrolled components?
Controlled components store input values in React state, giving you full control over the data. Uncontrolled components let the DOM manage their own values, accessed via useRef. Controlled inputs are standard for most form handling and validation scenarios.
When should I use React Context vs Redux?
Use Context for low-frequency data like theme or current user. Use Redux, Zustand, or Jotai when state updates frequently, spans many components, or needs middleware like persistence or logging. Context re-renders every consumer on each value change, which matters at scale.
What does the key prop do in React lists?
The key prop helps React identify which list items changed, were added, or removed during reconciliation. Keys must be stable and unique among siblings. Using array indexes as keys causes bugs when items reorder. Always prefer database IDs or stable identifiers.
What is React.memo and when should I use it?
React.memo wraps a functional component and skips re-rendering when its props haven't changed (shallow comparison). Use it when a component renders frequently with identical props. Don't apply it everywhere — the comparison itself has overhead, and the React Compiler handles most of this automatically in React 19.
What is the difference between useEffect and useLayoutEffect?
useEffect fires asynchronously after the browser paints. useLayoutEffect fires synchronously after DOM mutations but before paint. Use useLayoutEffect for DOM measurements where you need to read layout before the user sees the frame. For everything else, useEffect is the right choice.