ultimate reference

React Cheat Sheet

In this React cheat sheet, you'll find every hook, pattern, and API. Searchable, filterable by React version, one-click copy.

/ to focus
react version:
JSX Basics
16+
core
JSX element - compiles to React.createElement()
const el = <h1>Hello, world!</h1>;
Embed a JS expression with curly braces
const name = 'React'; const el = <h1>Hello, {name}!</h1>;
Fragment - wrap siblings without adding a DOM node
<> <h1>Title</h1> <p>Paragraph</p> </> <React.Fragment key={id}>...</React.Fragment> {/* keyed form */}
className instead of class; htmlFor instead of for
<div className="box"> <label htmlFor="email">Email</label> <input id="email" type="text" /> </div>
Inline styles use an object with camelCase property names
<p style={{ color: 'red', fontSize: '16px', marginTop: 0 }}>Text</p>
Conditional rendering - ternary and short-circuit
{isLoggedIn ? <Dashboard /> : <Login />} {showBanner && <Banner />} {count > 0 && <p>{count} items</p>} {/* careful: 0 renders "0" */}
Render a list - key must be stable and unique, never use array index
const items = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]; return ( <ul> {items.map(item => ( <li key={item.id}>{item.name}</li> ))} </ul> );
Component Anatomy
16+
core
Basic function component - name must start with a capital letter
function Greeting() { return <h1>Hello!</h1>; } export default Greeting;
Component with destructured props and default values
function Button({ label = 'Click me', onClick, disabled = false }) { return ( <button onClick={onClick} disabled={disabled}> {label} </button> ); }
Spread remaining props onto the native element
function Input({ className = '', ...rest }) { return <input className={`field ${className}`} {...rest} />; }
children prop - pass arbitrary content inside a component
function Card({ title, children }) { return ( <div className="card"> <h2>{title}</h2> {children} </div> ); } <Card title="News"><p>Content here</p></Card>
Rendering & Portals
18
core
Mount app with React 18 createRoot (replaces ReactDOM.render)
import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root')); root.render(<App />);
React 18
Portal - render into a DOM node outside the React root
import { createPortal } from 'react-dom'; function Modal({ children }) { return createPortal( <div className="modal">{children}</div>, document.getElementById('modal-root') ); }
useState
16.8+
hooks
When to use: local UI state that belongs to one component. Use useReducer instead when the next state depends on multiple conditions or you have several related fields.
Declare a state variable
const [count, setCount] = useState(0);
Functional update - use when next value depends on previous
setCount(prev => prev + 1); // safe in batched/async contexts setCount(count + 1); // can go stale if batched
Object state - always spread to preserve other fields
const [user, setUser] = useState({ name: '', age: 0 }); setUser(prev => ({ ...prev, name: 'Alice' }));
Lazy initial state - function runs once on mount only
const [data, setData] = useState(() => JSON.parse(localStorage.getItem('data')) ?? [] );
useReducer
16.8+
hooks
useState vs useReducer: use useReducer when you have multiple related fields that update together, or when transitions are complex enough to warrant a switch/case. useState wins for single values or simple toggles.
Full useReducer pattern with typed actions
const initialState = { count: 0, status: 'idle' }; function reducer(state, action) { switch (action.type) { case 'increment': return { ...state, count: state.count + 1 }; case 'decrement': return { ...state, count: state.count - 1 }; case 'reset': return initialState; default: throw new Error('Unknown action: ' + action.type); } } const [state, dispatch] = useReducer(reducer, initialState); dispatch({ type: 'increment' });
Event Handling
16+
core
Pass a function reference, not a call (omit parentheses)
<button onClick={handleClick}>OK</button> // correct <button onClick={handleClick()}>OK</button> // fires on every render!
Pass an argument by wrapping in an arrow function
<button onClick={() => handleDelete(item.id)}>Delete</button>
Prevent default and stop propagation
function handleSubmit(e) { e.preventDefault(); // stop form navigation e.stopPropagation(); // stop bubbling to parent handlers }
Common synthetic event names
onClick onDoubleClick onMouseEnter onMouseLeave onChange onInput onFocus onBlur onKeyDown onKeyUp onSubmit onScroll onWheel onDrag onDrop
useEffect
16.8+
hooks
When to use: synchronizing with something outside React - fetching, subscriptions, timers, DOM manipulation. Do not use for computing derived state; do that inline during render instead.
Run once on mount (empty deps array)
useEffect(() => { initializeSomething(); }, []); // runs once after first render
Run when specific values change
useEffect(() => { document.title = `${count} messages`; }, [count]); // re-runs whenever count changes
Cleanup - return a function to unsubscribe, clear timers, abort
useEffect(() => { const sub = eventBus.subscribe(channel, handler); return () => sub.unsubscribe(); // runs before next effect + on unmount }, [channel]);
Data fetching with AbortController to prevent stale state
const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const ctrl = new AbortController(); setLoading(true); fetch(`/api/users/${userId}`, { signal: ctrl.signal }) .then(r => { if (!r.ok) throw new Error(r.status); return r.json(); }) .then(setData) .catch(err => { if (err.name !== 'AbortError') setError(err); }) .finally(() => setLoading(false)); return () => ctrl.abort(); }, [userId]);
useMemo & useCallback
16.8+
hooks
useMemo vs useCallback: useMemo caches a computed value. useCallback caches a function reference. Both need the same dependency array rules. Profile before adding either - premature memoization adds complexity with no gain.
useMemo - cache an expensive derived value
const sorted = useMemo(() => [...items].sort((a, b) => a.score - b.score), [items]);
useCallback - stable function reference to avoid breaking React.memo
const handleDelete = useCallback((id) => { setItems(prev => prev.filter(i => i.id !== id)); }, []); // setItems is stable so no deps needed
useId, useDebugValue, useSyncExternalStore
18
hooks
useId - stable unique ID for a11y label/input pairing (React 18+)
function EmailField() { const id = useId(); return ( <> <label htmlFor={id}>Email</label> <input id={id} type="email" /> </> ); }
React 18
useDebugValue - label a custom hook in React DevTools
function useOnlineStatus() { const isOnline = useSyncExternalStore(subscribe, getSnapshot); useDebugValue(isOnline ? 'Online' : 'Offline'); return isOnline; }
useSyncExternalStore - subscribe to external stores safely (React 18+)
const snapshot = useSyncExternalStore( store.subscribe, // fn(callback) - call callback when store changes store.getSnapshot, // fn() - return current value synchronously store.getSnapshot // optional: server snapshot for SSR );
React 18
useInsertionEffect - inject CSS-in-JS styles before DOM mutations (library authors only)
useInsertionEffect(() => { const style = document.createElement('style'); style.textContent = `.cls { color: red }`; document.head.appendChild(style); return () => document.head.removeChild(style); }, []);
Custom Hook Patterns
16.8+
pattern
useFetch - reusable data fetching with loading/error state
function useFetch(url) { const [state, setState] = useState({ data: null, loading: true, error: null }); useEffect(() => { let cancelled = false; setState({ data: null, loading: true, error: null }); fetch(url) .then(r => r.json()) .then(data => { if (!cancelled) setState({ data, loading: false, error: null }); }) .catch(error => { if (!cancelled) setState({ data: null, loading: false, error }); }); return () => { cancelled = true; }; }, [url]); return state; } const { data, loading, error } = useFetch('/api/posts');
useLocalStorage - persist state to localStorage
function useLocalStorage(key, initial) { const [value, setValue] = useState(() => { try { return JSON.parse(localStorage.getItem(key)) ?? initial; } catch { return initial; } }); const set = useCallback(v => { setValue(v); localStorage.setItem(key, JSON.stringify(v)); }, [key]); return [value, set]; }
Hook rules - only call at top level, only in React functions
// CORRECT - top level in component or custom hook function Component() { const [x] = useState(0); // ok if (x > 0) { doSomething(); } // condition on value, not on hook call // WRONG - never inside condition, loop, or nested function: // if (condition) { const [y] = useState(0); } }
useContext
16.8+
hooks
Context vs state libraries: use Context for truly global, infrequently-changing data (theme, locale, current user). For frequently-updating shared state, combine with useReducer or use Zustand - every consumer re-renders on every context value change.
Create, provide, and consume context
// 1. Create export const ThemeContext = createContext('light'); // 2. Provide function App() { const [theme, setTheme] = useState('dark'); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <Page /> </ThemeContext.Provider> ); } // 3. Consume anywhere in the tree function Button() { const { theme } = useContext(ThemeContext); return <button className={theme}>Toggle</button>; }
Context + useReducer pattern for a lightweight global store
const StoreCtx = createContext(null); function StoreProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreCtx.Provider value={{ state, dispatch }}> {children} </StoreCtx.Provider> ); } export const useStore = () => useContext(StoreCtx);
React 19: render Context directly - no .Provider needed
<ThemeContext value="dark"> <App /> </ThemeContext>
React 19
useRef
16.8+
hooks
useRef vs useState: both persist values between renders, but useRef does NOT trigger a re-render when changed. Use useRef for DOM access, timers, or any mutable value you need without re-rendering.
Access a DOM node imperatively
const inputRef = useRef(null); <input ref={inputRef} /> inputRef.current.focus();
Store a timer ID without causing re-renders
const timerRef = useRef(null); timerRef.current = setTimeout(callback, 1000); clearTimeout(timerRef.current);
Track previous value across renders
function usePrevious(value) { const ref = useRef(undefined); useEffect(() => { ref.current = value; }); return ref.current; // value from previous render }
forwardRef - forward a ref to a child DOM node
const Input = forwardRef(function Input(props, ref) { return <input ref={ref} {...props} />; }); const ref = useRef(null); <Input ref={ref} placeholder="Focus me" />
React 19: ref as a prop - no forwardRef wrapper needed
function Input({ ref, ...props }) { return <input ref={ref} {...props} />; }
React 19
useImperativeHandle - expose a custom API on a ref
const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => inputRef.current.focus(), clear: () => { inputRef.current.value = ''; } })); return <input ref={inputRef} {...props} />; });
Lifecycle Mapping
16+
note
componentDidMount equivalent - run once after mount
useEffect(() => { /* mount logic */ }, []);
componentDidUpdate equivalent - run when dep changes
useEffect(() => { /* update logic */ }, [dep]);
componentWillUnmount equivalent - return cleanup
useEffect(() => { return () => { /* cleanup on unmount */ }; }, []);
useLayoutEffect vs useEffect - synchronous after DOM paint, before user sees it
useLayoutEffect(() => { // Read DOM dimensions, fix layout positions - fires synchronously const { width } = ref.current.getBoundingClientRect(); setWidth(width); }, []); // Rule: prefer useEffect (non-blocking). Only use useLayoutEffect // when you need to prevent a visible flicker from a DOM measurement.
Error Boundaries
16+
advanced
Class error boundary - still required, no hook equivalent exists yet
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } componentDidCatch(error, info) { logError(error, info.componentStack); } render() { return this.state.hasError ? (this.props.fallback ?? <p>Something went wrong.</p>) : this.props.children; } } <ErrorBoundary fallback={<ErrorPage />}> <RiskyComponent /> </ErrorBoundary>
Suspense & Lazy Loading
16+
advanced
Lazy load a component to split the JS bundle
import { lazy, Suspense } from 'react'; const Chart = lazy(() => import('./Chart')); function App() { return ( <Suspense fallback={<Spinner />}> <Chart /> </Suspense> ); }
Compound Components
16+
pattern
When to use: a parent and its related sub-components share implicit state (Tabs+Tab, Select+Option, Accordion+Panel). Avoids prop drilling with a clean API.
Parent shares state with children via context
const TabsCtx = createContext(); function Tabs({ children, defaultIndex = 0 }) { const [active, setActive] = useState(defaultIndex); return ( <TabsCtx.Provider value={{ active, setActive }}> <div className="tabs">{children}</div> </TabsCtx.Provider> ); } Tabs.Tab = function Tab({ index, label }) { const { active, setActive } = useContext(TabsCtx); return <button className={active === index ? 'active' : ''} onClick={() => setActive(index)}>{label}</button>; }; Tabs.Panel = function Panel({ index, children }) { const { active } = useContext(TabsCtx); return active === index ? <div>{children}</div> : null; };
HOC & Render Props
16+
pattern
HOC vs Render Props vs Custom Hooks: custom hooks are the modern default for sharing logic. HOCs still appear for cross-cutting concerns (auth guards, analytics wrappers) and in library APIs. Render props remain useful when the consumer needs to control rendering structure.
HOC - wrap a component to inject behavior
function withAuth(WrappedComponent) { return function AuthGuard(props) { const { user } = useContext(AuthContext); if (!user) return <Redirect to="/login" />; return <WrappedComponent {...props} user={user} />; }; } const ProtectedDashboard = withAuth(Dashboard);
Render props - pass state via children function
function Toggle({ children }) { const [on, setOn] = useState(false); return children({ on, toggle: () => setOn(v => !v) }); } <Toggle> {({ on, toggle }) => <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>} </Toggle>
Controlled vs Uncontrolled
16+
note
Controlled - React owns value; best for validation, dynamic UIs
const [name, setName] = useState(''); <input value={name} onChange={e => setName(e.target.value)} />
Uncontrolled - DOM owns value; best for simple forms and file inputs
const nameRef = useRef(null); <input ref={nameRef} defaultValue="Alice" /> // Read on submit: nameRef.current.value
Accessibility Patterns
16+
core
Return focus after a modal closes
const triggerRef = useRef(null); const [open, setOpen] = useState(false); const handleClose = () => { setOpen(false); triggerRef.current?.focus(); // return focus to the button that opened modal };
ARIA attributes in JSX
<button aria-label="Close dialog" aria-expanded={open} onClick={close}> <Icon /> </button> <div role="dialog" aria-modal="true" aria-labelledby={titleId}> <h2 id={titleId}>Confirm Delete</h2> </div>
useId for paired label/input (React 18+)
function Field({ label, type = 'text' }) { const id = useId(); const errorId = useId(); return ( <div> <label htmlFor={id}>{label}</label> <input id={id} type={type} aria-describedby={errorId} /> <span id={errorId} role="alert"></span> </div> ); }
React 18
React.memo
16+
advanced
When to use: skip re-renders when a component gets the same props repeatedly and rendering is expensive. Pair with useCallback on callback props. Skip memo for cheap components or ones that always receive new props.
Skip re-render when props are shallowly equal
const Row = React.memo(function Row({ id, name, onDelete }) { return <li>{name} <button onClick={() => onDelete(id)}>x</button></li>; });
Custom comparator for selective equality
const List = React.memo(Component, (prev, next) => prev.id === next.id && prev.items.length === next.items.length );
useTransition & useDeferredValue
18
advanced
useTransition vs useDeferredValue: use useTransition when you control the state setter (wrap the slow update). Use useDeferredValue when you receive the value as a prop you cannot control. Both keep input responsive by deferring slow renders.
useTransition - mark an update as non-urgent so fast updates win
const [isPending, startTransition] = useTransition(); function handleSearch(query) { setQuery(query); // urgent - update input immediately startTransition(() => { setResults(filterLargeList(query)); // non-urgent - can be interrupted }); } {isPending && <Spinner />}
React 18
useDeferredValue - defer re-rendering a slow subtree
const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; <div style={{ opacity: isStale ? 0.5 : 1 }}> <SlowList query={deferredQuery} /> </div>
React 18
Code Splitting & Keys
16+
advanced
Route-based code splitting with lazy + Suspense
const Home = lazy(() => import('./pages/Home')); const Settings = lazy(() => import('./pages/Settings')); // Each page's JS loads only when first visited
key trick - force a full component reset when identity changes
<UserProfile key={userId} userId={userId} /> // Changing key unmounts + remounts instead of updating. // Useful when componentDidMount logic must re-run.
Setup & Routes
library
pattern
Wrap app and declare routes
import { BrowserRouter, Routes, Route } from 'react-router-dom'; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/user/:id" element={<User />} /> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); }
react-router-dom
Nested routes with Outlet
<Route path="/dashboard" element={<DashLayout />}> <Route index element={<Overview />} /> <Route path="stats" element={<Stats />} /> </Route> function DashLayout() { return <div><Sidebar /><Outlet /></div>; // Outlet renders child route }
Link, NavLink, useParams, useNavigate, useSearchParams
import { Link, NavLink, useParams, useNavigate, useSearchParams } from 'react-router-dom'; <Link to="/about">About</Link> <NavLink to="/about" className={({ isActive }) => isActive ? 'active' : ''}>About</NavLink> const { id } = useParams(); const navigate = useNavigate(); navigate('/dashboard'); navigate(-1); // go back const [params, setParams] = useSearchParams(); const q = params.get('q') ?? '';
Controlled Forms
16+
core
Multi-field form with a single generic onChange handler
const [form, setForm] = useState({ email: '', password: '', remember: false }); const handleChange = ({ target: { name, value, type, checked } }) => setForm(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); <form onSubmit={handleSubmit}> <input name="email" value={form.email} onChange={handleChange} type="email" /> <input name="password" value={form.password} onChange={handleChange} type="password" /> <input name="remember" checked={form.remember} onChange={handleChange} type="checkbox" /> <button type="submit">Login</button> </form>
Select and textarea
<select value={role} onChange={e => setRole(e.target.value)}> <option value="">Select role</option> <option value="admin">Admin</option> <option value="user">User</option> </select> <textarea value={bio} onChange={e => setBio(e.target.value)} />
Props & Children Types
16+
typescript
Define props with an interface
interface ButtonProps { label: string; onClick: () => void; disabled?: boolean; variant?: 'primary' | 'secondary'; } function Button({ label, onClick, disabled = false, variant = 'primary' }: ButtonProps) { return <button onClick={onClick} disabled={disabled} className={variant}>{label}</button>; }
ReactNode vs PropsWithChildren
import { type ReactNode, type PropsWithChildren } from 'react'; interface CardProps { children: ReactNode; title: string; } // PropsWithChildren shorthand function Wrapper({ children }: PropsWithChildren) { return <div>{children}</div>; }
Extend HTML element props to pass native attributes through
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { label: string; error?: string; } function Input({ label, error, ...rest }: InputProps) { return ( <> <label>{label}</label> <input {...rest} /> {error && <span>{error}</span>} </> ); }
FC type (optional - function declaration preferred in modern code)
import { type FC } from 'react'; const Greeting: FC<{ name: string }> = ({ name }) => <h1>Hello, {name}!</h1>; // Note: FC does NOT automatically include children in modern @types/react.
useState, useRef, useContext
16+
typescript
useState - inferred or explicit generic
const [count, setCount] = useState(0); // inferred: number const [user, setUser] = useState<User | null>(null); // explicit union const [items, setItems] = useState<string[]>([]); // explicit array
useRef - DOM ref vs mutable value ref
const inputRef = useRef<HTMLInputElement>(null); // DOM ref const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined); // mutable
useContext with a null-safe custom hook
interface AuthCtxType { user: User | null; login: (email: string, password: string) => Promise<void>; } const AuthContext = createContext<AuthCtxType | null>(null); function useAuth(): AuthCtxType { const ctx = useContext(AuthContext); if (!ctx) throw new Error('useAuth must be inside AuthProvider'); return ctx; }
Event Types
16+
typescript
Common React event type signatures
(e: React.MouseEvent<HTMLButtonElement>) => void // onClick (e: React.ChangeEvent<HTMLInputElement>) => void // onChange input (e: React.FormEvent<HTMLFormElement>) => void // onSubmit (e: React.KeyboardEvent<HTMLInputElement>) => void // onKeyDown (e: React.DragEvent<HTMLDivElement>) => void // onDrag
Hook Compatibility Table
16+
reference
Minimum React version per hook or feature
Hook / FeatureMin VersionNotes
useState, useEffect, useContext, useRef, useCallback, useMemo, useReducer16.8All original hooks
useId18Stable IDs for a11y, SSR-safe
useTransition18Concurrent mode
useDeferredValue18Concurrent mode
useSyncExternalStore18External store integration
useInsertionEffect18CSS-in-JS library authors
useActionState19Replaces useFormState
useOptimistic19Optimistic UI updates
use()19Read Promise/context in render
ref as prop (no forwardRef)19Simplifies ref forwarding
Context without .Provider19Render Context directly
Render & Query
library
testing
Render and find an element
import { render, screen } from '@testing-library/react'; test('shows greeting', () => { render(<Greeting name="Alice" />); expect(screen.getByText('Hello, Alice!')).toBeInTheDocument(); });
@testing-library/react
Query priority - use the most accessible query first
screen.getByRole('button', { name: 'Submit' }) // best - mirrors how users find it screen.getByLabelText('Email') // great for form fields screen.getByPlaceholderText('Search...') // ok fallback screen.getByText('Hello') // visible text screen.getByTestId('submit-btn') // last resort only // Variants: getBy throws, queryBy returns null, findBy is async (returns Promise)
User Interactions
library
testing
userEvent v14 - more realistic than fireEvent
import userEvent from '@testing-library/user-event'; test('submits the form', async () => { const user = userEvent.setup(); const onSubmit = jest.fn(); render(<LoginForm onSubmit={onSubmit} />); await user.type(screen.getByLabelText('Email'), 'alice@example.com'); await user.type(screen.getByLabelText('Password'), 'secret'); await user.click(screen.getByRole('button', { name: 'Login' })); expect(onSubmit).toHaveBeenCalledWith({ email: 'alice@example.com', password: 'secret' }); });
@testing-library/user-event
waitFor - assert after async state updates
import { waitFor } from '@testing-library/react'; test('loads and shows data', async () => { render(<UserList />); expect(screen.getByText('Loading...')).toBeInTheDocument(); await waitFor(() => expect(screen.getByText('Alice')).toBeInTheDocument()); });
Mocking & Custom Hooks
library
testing
Mock a module
jest.mock('./api', () => ({ fetchUsers: jest.fn(() => Promise.resolve([{ id: 1, name: 'Alice' }])) }));
Test a custom hook with renderHook
import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter'; test('increments counter', () => { const { result } = renderHook(() => useCounter(0)); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); });
Wrap with context providers in tests
function Wrapper({ children }) { return ( <AuthProvider> <ThemeProvider theme="light">{children}</ThemeProvider> </AuthProvider> ); } render(<Dashboard />, { wrapper: Wrapper });
Zustand
library
pattern
Zustand over Context when: state updates are frequent, you need fine-grained subscriptions (only re-render components subscribed to a changed slice), you want devtools/middleware, or you need global state without wrapping the tree.
Create a store with state and actions
import { create } from 'zustand'; const useStore = create((set) => ({ count: 0, user: null, increment: () => set((state) => ({ count: state.count + 1 })), setUser: (user) => set({ user }), reset: () => set({ count: 0, user: null }), }));
zustand
Subscribe to a slice - only re-renders when that value changes
const count = useStore((state) => state.count); // re-renders on count change only const increment = useStore((state) => state.increment); // stable - never triggers re-render
persist middleware - persist state to localStorage
import { create } from 'zustand'; import { persist } from 'zustand/middleware'; const useSettingsStore = create( persist( (set) => ({ theme: 'light', setTheme: (theme) => set({ theme }), }), { name: 'settings-storage' } // key in localStorage ) );
Jotai
library
pattern
Jotai over Zustand when: you prefer atomic bottom-up state (compose small atoms instead of one store), need React Suspense integration out of the box, or want derived atoms that automatically recompute like spreadsheet cells.
Define atoms and use them like useState - but globally
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; const countAtom = atom(0); const userAtom = atom(null); function Counter() { const [count, setCount] = useAtom(countAtom); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; } const count = useAtomValue(countAtom); // read-only const setCount = useSetAtom(countAtom); // write-only (avoids re-render on read)
jotai
Derived atom - automatically recalculates when dependencies change
const priceAtom = atom(100); const taxAtom = atom(0.2); const totalAtom = atom((get) => get(priceAtom) * (1 + get(taxAtom)));
Context vs Zustand vs Jotai
guide
Which to use for which scenario
ScenarioBest choice
Theme, locale, current userContext
Complex forms with related fieldsuseReducer + Context
Shopping cart, auth, app-wide stateZustand
Frequently updating shared valuesZustand (slice selectors)
Independent atomic state piecesJotai
Server state (fetch, cache, sync)TanStack Query
useActionState
19
advanced
When to use: async form mutations. Replaces the manual pattern of tracking isPending, error, and result state around form submissions.
Handle async form actions with pending/error/result in one hook
import { useActionState } from 'react'; async function updateUsername(prevState, formData) { const username = formData.get('username'); if (username.length < 3) return { error: 'Too short' }; await api.updateUsername(username); return { success: true }; } function UsernameForm() { const [state, formAction, isPending] = useActionState(updateUsername, null); return ( <form action={formAction}> <input name="username" required /> <button disabled={isPending}>{isPending ? 'Saving...' : 'Save'}</button> {state?.error && <p>{state.error}</p>} {state?.success && <p>Updated!</p>} </form> ); }
React 19
useOptimistic
19
advanced
Show an optimistic UI update immediately; revert automatically if server fails
const [optimisticItems, addOptimistic] = useOptimistic( serverItems, (currentState, newItem) => [...currentState, { ...newItem, pending: true }] ); async function handleAdd(item) { addOptimistic(item); // instant UI update await api.createItem(item); // wait for server; reverts on error }
React 19
use() hook
19
advanced
Read a Promise or Context during render - can be called conditionally unlike other hooks
import { use } from 'react'; // Suspends until the promise resolves function UserProfile({ userPromise }) { const user = use(userPromise); return <h1>{user.name}</h1>; } // Read context conditionally - only valid with use() function MaybeThemed({ themed }) { if (themed) { const theme = use(ThemeContext); // allowed! return <div className={theme}>...</div>; } return <div>...</div>; }
React 19
RSC, use client, use server
19
advanced
Server Component - runs on server only, zero JS sent to browser
// app/page.tsx (Next.js App Router) - Server Component by default async function Page() { const data = await db.query('SELECT * FROM posts'); // direct DB access return <PostList posts={data} />; // No useState, useEffect, or event handlers here }
RSC
'use client' - opt a file into Client Component rendering for hooks and events
'use client'; // must be first line of the file import { useState } from 'react'; export function LikeButton({ postId }) { const [liked, setLiked] = useState(false); return <button onClick={() => setLiked(v => !v)}>{liked ? 'Liked' : 'Like'}</button>; }
RSC
'use server' - Server Action that can be called from a Client Component
'use server'; export async function deletePost(postId) { await db.posts.delete({ where: { id: postId } }); revalidatePath('/posts'); // Next.js - invalidate cache } // In a Client Component: // import { deletePost } from './actions'; // <button onClick={() => deletePost(id)}>Delete</button>
RSC

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, createRoot

Non-blocking UI updates

React 19

React Compiler, use() hook

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 this keyword, 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 via this.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:

  • className not classclass is a reserved JavaScript keyword

  • htmlFor not for — same reason, for conflicts 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

useState

Local component state

Form inputs, toggles, counters

useEffect

Side effects

API calls, subscriptions, timers

useContext

Consume context

Theme, auth, locale

useRef

DOM refs and persistent values

Input focus, animation, previous value

useReducer

Complex state logic

Multi-step forms, shopping carts

useMemo

Memoize computed values

Expensive calculations, filtered lists

useCallback

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

componentDidMount

useEffect(() => {}, [])

Update

componentDidUpdate

useEffect(() => {}, [dep])

Unmount

componentWillUnmount

useEffect cleanup return

Render optimization

shouldComponentUpdate

React.memo

Before paint

getSnapshotBeforeUpdate

useLayoutEffect

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

React.createContext(defaultValue)

Creates the context object

Provide

<Context.Provider value={...}>

Makes value available to the tree

Consume

useContext(MyContext)

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

BrowserRouter

Wraps the app, provides routing context

Routes + Route

Define URL-to-component mappings

Link / NavLink

Client-side navigation (no page reload)

useNavigate

Programmatic navigation

useParams

Read dynamic URL parameters

Outlet

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, breaking React.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.