React

Ready-to-copy starter snippets for new components and hooks, plus components and JSX, props and state, the built-in hooks, effects and refs, context and composition, and memoization.

Every React hook / API at a glance

Every entry, one line each. Highlighted ones have a detailed example below — click to jump there.

All 78 entries
JSX
A syntax extension that lets HTML-like markup describe UI inside JavaScript
Fragment (<>...</>)
Groups sibling elements without adding an extra node to the rendered DOM
key prop
Tells React which array item a rendered element corresponds to across renders
children prop
The special prop holding whatever is nested inside a component's tags
Conditional rendering
Choosing what to render with a ternary, &&, or an early return
Controlled component
A form element whose value is driven entirely by React state
Uncontrolled component
A form element that manages its own value internally, read via a ref
Component
A function that returns JSX describing what a piece of UI looks like
Virtual DOM
React's lightweight in-memory representation of the UI, diffed to find real DOM changes
Reconciliation
The process React uses to compare renders and apply the minimal DOM update
Render prop
A prop whose value is a function returning JSX, letting the caller control rendering
Higher-order component (HOC)
A function that takes a component and returns a new, enhanced component
Compound components
Components designed to be used together, sharing implicit state (e.g. Tabs.Tab)
Portal
Renders a child into a DOM node outside its parent's DOM hierarchy
Props
Read-only data passed from a parent component into a child
Prop drilling
Passing a prop through components that don't use it, just to reach one that does
useState
Declares a piece of local state and a setter function
Lifting state up
Moving shared state to the closest common ancestor of the components that need it
Derived state
A value computed from existing state or props during render, instead of stored separately
Controlled input
value and onChange both wired to state, so React is the single source of truth
Lazy state initializer
Passing a function to useState so the initial value is computed only once
Batching
React grouping multiple state updates into a single re-render
useReducer
Manages state transitions via a reducer function and dispatched actions
Immutability
State should be replaced with new objects or arrays, never mutated in place
Single source of truth
Each piece of state should live in exactly one place in the component tree
Default props
Fallback values a component uses when a prop is not passed
useState
Declares a piece of state and returns its value plus a setter
useEffect
Runs a side effect after render, re-running when its dependencies change
useContext
Reads the nearest matching Provider's value
useRef
Holds a mutable value across renders without causing a re-render
useMemo
Caches an expensive computed value between renders
useCallback
Caches a function reference between renders
useReducer
A reducer-based alternative to useState for more complex state transitions
useLayoutEffect
Like useEffect, but fires synchronously before the browser paints
useId
Generates a stable unique id, safe for use with server rendering
useTransition
Marks a state update as low-priority and non-blocking
useDeferredValue
Defers re-rendering a value until higher-priority updates finish
useSyncExternalStore
Subscribes safely to a store that lives outside React state
Custom hook
A reusable function that calls other hooks, named with a 'use' prefix
Rules of Hooks
Call hooks only at the top level, and only from React function components or hooks
useEffect
Runs a side effect after render, re-running when dependencies change
Dependency array
The list of values an effect watches to decide whether to re-run
Cleanup function
The function an effect returns, run before the next effect or on unmount
useRef
Persists a mutable value across renders without causing one
ref attribute
Attaches a ref to a DOM node or component instance
forwardRef
Forwards a ref through a component to one of its children
useImperativeHandle
Customizes what a forwarded ref exposes to its parent
useLayoutEffect
An effect variant that fires synchronously before the browser paints
Effect vs event handler
Effects sync with the outside world; event handlers respond to a specific interaction
Stale closure
A callback capturing an old value from a previous render, a common effect bug
Mount / unmount
The lifecycle points when a component is first inserted or removed from the tree
AbortController in effects
Common pattern for cancelling an in-flight fetch on cleanup
createContext
Creates a Context object with a default value
Provider
Supplies a Context's value to all of its descendants
useContext
Reads the nearest matching Provider's value
Context re-render cost
Every consumer re-renders when the Provider value changes, regardless of depth
Composition
Building UI by combining smaller components rather than extending a base one
children as a slot
Passing a whole element through children instead of many boolean props
Compound components
A parent-child component set sharing implicit state through context
Render prop
Sharing logic by passing a function prop that returns JSX
Higher-order component (HOC)
Wraps a component to add behavior shared across several components
Multiple contexts
Splitting unrelated global values into separate contexts to limit re-renders
Context selector pattern
Reading only part of a context value to avoid unnecessary re-renders
No component inheritance
React has no built-in mechanism for one component to extend another
useMemo
Caches a computed value between renders
useCallback
Caches a function reference between renders
React.memo
Skips re-rendering a component when its props haven't shallowly changed
Reconciliation
React's diffing process for turning a new render into minimal DOM changes
Virtual DOM
The in-memory tree React diffs before touching the real DOM
Shallow comparison
Comparing object or array references, not their contents, one level deep
Code splitting
Breaking a bundle into smaller chunks loaded on demand
React.lazy
Loads a component's code only when it's first rendered
Suspense
Shows fallback UI while a lazy component or async data is loading
StrictMode
Development-only wrapper that double-invokes functions to surface unsafe side effects
Windowing / virtualization
Rendering only the visible slice of a long list
key stability
Using stable, unique keys so React doesn't discard and recreate list items unnecessarily
Profiler
React DevTools tool for measuring what renders and why
Concurrent rendering
React preparing multiple versions of the UI so urgent updates aren't blocked

Worked examples with live previews, for the parts worth seeing in action.

Starter Templates

Project Setup Commands

# Framework path — what react.dev itself recommends starting from
npx create-next-app@latest my-app --typescript

cd my-app
npm run dev
# Plain-Vite path — no framework, just React + a build tool. What every other
# starter on this page assumes, since this cheat sheet covers React itself,
# not a specific framework's routing/data-fetching APIs.
npm create vite@latest my-app -- --template react-ts

cd my-app
npm install
npm run dev

# Build for production, then preview that build locally
npm run build
npm run preview

A couple of commonly-needed extras on top of the plain-Vite path:

# Client-side routing (framework-free — Next.js has its own router built in)
npm install react-router-dom

# Fetching/caching server state (an alternative to hand-rolled useFetch hooks)
npm install @tanstack/react-query

If you’re building a real product, start with the framework path — react.dev is explicit that it’s the recommended default now, since it solves routing, data fetching, and server rendering you’d otherwise have to wire up by hand. The Vite path is what this page’s other starters assume, since they’re about React itself rather than any one framework’s conventions.

React's own official guidance and the plain-Vite alternative — which to reach for depends on whether you need a full framework or not.

react.dev recommends a framework
Next.js is the officially recommended starting point for a new production React app — it handles routing, data fetching, and server rendering for you
npm create vite@latest
The right choice for a plain client-side app with no framework — learning React, a widget, or an SPA that doesn't need server rendering
npm run dev
Starts the local dev server with hot module replacement (both paths below)
  • setup
  • vite
  • nextjs
  • cli
  • boilerplate

Function Component Starter

interface CardProps {
  title: string;
  onSelect?: () => void;
}

function Card({ title, onSelect }: CardProps) {
  return (
    <div className="card" onClick={onSelect}>
      <h3>{title}</h3>
    </div>
  );
}

export default Card;

Copy this, rename Card/CardProps, and fill in the markup — this is the skeleton for the large majority of components that just take some data and render it, with no internal state of their own.

The shape almost every new component file starts from — typed props, destructured, one default export.

Props interface
Named after the component, e.g. CardProps — keeps prop types readable in editor tooltips
Optional prop (?)
Use ? for props a caller can omit, and give it sensible fallback behavior in the body
Default export
One component per file convention — makes the file easy to find by its export
  • component
  • props
  • typescript
  • boilerplate

Component with Children Starter

interface PanelProps {
  title: string;
  children: React.ReactNode;
}

function Panel({ title, children }: PanelProps) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      <div className="panel-body">{children}</div>
    </section>
  );
}

export default Panel;

The difference from a plain component starter is just the children prop — typed as React.ReactNode rather than a specific value — which lets the caller pass any markup through instead of a fixed set of props.

For a component that wraps arbitrary markup a caller provides — a panel, a card, a layout shell.

React.ReactNode
The type for children — covers elements, strings, numbers, fragments, and null/undefined
Nesting
Called as <Panel title="Notes">...content...</Panel> — whatever is between the tags becomes children
  • component
  • children
  • typescript
  • boilerplate

Component with Local State Starter

import { useState } from 'react';

interface CounterProps {
  initialCount?: number;
}

function Counter({ initialCount = 0 }: CounterProps) {
  const [count, setCount] = useState(initialCount);

  return (
    <button onClick={() => setCount((c) => c + 1)}>
      Count: {count}
    </button>
  );
}

export default Counter;

This is the same shape as the plain component starter with one useState call added — copy it as the starting point any time a component needs to remember something between renders that isn’t passed in from a parent.

For a component that manages its own value — a counter, a toggle, a small piece of form state.

Default via destructuring
initialCount = 0 supplies a fallback when the caller omits the prop entirely
Functional update
setCount((c) => c + 1) reads the latest value instead of a possibly-stale closure
  • component
  • usestate
  • typescript
  • boilerplate

forwardRef Component Starter

import { forwardRef } from 'react';

interface TextInputProps {
  label: string;
}

const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
  ({ label }, ref) => (
    <label>
      {label}
      <input ref={ref} />
    </label>
  )
);

TextInput.displayName = 'TextInput';

export default TextInput;

A parent can now do <TextInput ref={inputRef} label="Name" /> and call inputRef.current.focus() — without forwardRef, the ref prop would be silently dropped rather than reaching the underlying <input>.

For a component whose underlying DOM node a parent needs direct access to — a custom input, a focusable widget.

forwardRef<T, P>
First type parameter is the ref target (the DOM node type), second is the props type
displayName
Set explicitly — forwardRef-wrapped components otherwise show up unnamed in DevTools
When to use it
Only when a parent genuinely needs the DOM node itself — most components never need this
  • component
  • forwardref
  • typescript
  • boilerplate

Custom Hook Starter

import { useState, useCallback } from 'react';

function useToggle(initial = false) {
  const [value, setValue] = useState(initial);

  const toggle = useCallback(() => setValue((v) => !v), []);

  return [value, toggle] as const;
}

export default useToggle;

Used as const [isOpen, toggleOpen] = useToggle(); — copy this shape for any bit of stateful logic (a toggle, a fetch, a subscription) that more than one component needs, and rename useToggle/value/toggle to fit.

The shape a new custom hook starts from — internal state, a stable API, one clear return value.

use prefix
Required, not just convention — it is how React and lint rules recognize a function as a hook
as const
Locks the return tuple's element types/order, so callers get [value, toggle] instead of a loose array
useCallback
Keeps the returned function reference stable across renders, so it is safe to use as a dependency
  • hooks
  • custom hook
  • typescript
  • boilerplate

Context + Provider + Hook Starter

import { createContext, useContext, useState, type ReactNode } from 'react';

interface ThemeContextValue {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  const toggleTheme = () => setTheme((t) => (t === 'light' ? 'dark' : 'light'));

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');
  return ctx;
}

Wrap the subtree once in <ThemeProvider>, then any component inside it just calls const { theme, toggleTheme } = useTheme(); — no prop drilling, and the guard clause catches a missing provider immediately instead of a confusing undefined further down the tree.

The full pattern for sharing a value across a subtree — a context, its provider, and a hook that reads it safely.

Undefined default
createContext<T | undefined>(undefined) — forces every consumer through the hook, never the raw context
Guard clause
Throwing when the hook is called outside its provider turns a silent bug into an immediate, clear error
value memoization
Wrap the provider value in useMemo if it is an object, so consumers only re-render when it actually changes
  • context
  • provider
  • custom hook
  • typescript
  • boilerplate

Data-Fetching Hook Starter

import { useState, useEffect } from 'react';

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const controller = new AbortController();
    setLoading(true);

    fetch(url, { signal: controller.signal })
      .then((res) => res.json())
      .then((json: T) => setData(json))
      .catch((err) => {
        if (err.name !== 'AbortError') setError(err);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();
  }, [url]);

  return { data, error, loading };
}

export default useFetch;

Used as const { data, error, loading } = useFetch<User[]>('/api/users'); — copy this shape for any request, and swap fetch for a project’s own API client if it has one; the state shape and cleanup stay the same.

A custom hook covering the three states every fetch needs — loading, error, and data — with cleanup on unmount.

Three-state shape
{ data, error, loading } — lets the calling component render each case explicitly
AbortController
Cancels the in-flight request on cleanup, so a fast unmount or url change never sets state on a dead component
Dependency on url
Re-runs the fetch whenever the url argument changes, exactly like any other useEffect
  • hooks
  • custom hook
  • fetch
  • typescript
  • boilerplate

Error Boundary Starter

import { Component, type ReactNode } from 'react';

interface Props {
  fallback: ReactNode;
  children: ReactNode;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error: Error) {
    console.error('ErrorBoundary caught:', error);
  }

  render() {
    return this.state.hasError ? this.props.fallback : this.props.children;
  }
}

export default ErrorBoundary;

Used as <ErrorBoundary fallback={<p>Something went wrong.</p>}>...</ErrorBoundary> around a specific widget or route — this is still a class component because React has no hook equivalent for catching render errors in a subtree.

The one component type hooks still cannot replace — catches render errors in its subtree and shows a fallback instead of a blank screen.

getDerivedStateFromError
A static method that turns a caught error into fallback-render state — required, and cannot be a hook
componentDidCatch
Where to log the error (to a monitoring service, console, etc.) — separate from deciding what to render
Scope it tightly
Wrap one risky subtree (a widget, a route), not the whole app — so one failure does not blank everything
  • error boundary
  • class component
  • typescript
  • boilerplate

Compound Component Starter

import { createContext, useContext, useState, type ReactNode } from 'react';

const TabsContext = createContext<{ active: string; setActive: (id: string) => void } | undefined>(undefined);

function Tabs({ defaultTab, children }: { defaultTab: string; children: ReactNode }) {
  const [active, setActive] = useState(defaultTab);
  return <TabsContext.Provider value={{ active, setActive }}>{children}</TabsContext.Provider>;
}

function Tab({ id, children }: { id: string; children: ReactNode }) {
  const ctx = useContext(TabsContext);
  if (!ctx) throw new Error('Tabs.Tab must be used within <Tabs>');
  return (
    <button aria-pressed={ctx.active === id} onClick={() => ctx.setActive(id)}>
      {children}
    </button>
  );
}

Tabs.Tab = Tab;
export default Tabs;

Used as <Tabs defaultTab="a"><Tabs.Tab id="a">First</Tabs.Tab><Tabs.Tab id="b">Second</Tabs.Tab></Tabs> — the same context-plus-hook idea as the standalone context starter, just scoped to one component family instead of a whole subtree.

A component family that shares implicit state via context, so callers compose it like <Tabs><Tabs.Tab /></Tabs> instead of passing config props.

Attached sub-components
Tabs.Tab is just Tab assigned onto the Tabs function — no extra module or export needed
Implicit shared state
The active tab lives in context, so Tab never needs an index prop wired up by the caller
Composable markup
Callers control order, spacing, and what sits between tabs — a fixed-props API could not offer that
  • compound components
  • context
  • typescript
  • boilerplate

Lazy-Loaded Component Starter

import { lazy, Suspense } from 'react';

const ChartPanel = lazy(() => import('./ChartPanel'));

function Dashboard() {
  return (
    <Suspense fallback={<p>Loading chart…</p>}>
      <ChartPanel />
    </Suspense>
  );
}

export default Dashboard;

ChartPanel’s code is not downloaded until Dashboard actually renders it — reach for this for anything large and not needed on first paint (a chart library, a modal’s contents, a rarely-visited settings panel).

Splits a heavy component into its own chunk, loaded only when it is actually rendered.

React.lazy
Takes a dynamic import() and returns a component that resolves it on first render
Suspense fallback
Required wrapper — shown while the chunk is downloading, not just on slow networks
Default export only
React.lazy expects the imported module to have a default export
  • lazy
  • suspense
  • code splitting
  • typescript
  • boilerplate

Components & JSX

JSX Syntax & Expressions

function ProductCard({ product }) {
  return (
    <>
      <h3>{product.name}</h3>
      <p>{product.inStock ? 'In stock' : 'Out of stock'}</p>
      <ul>
        {product.tags.map((tag) => (
          <li key={tag}>{tag}</li>
        ))}
      </ul>
    </>
  );
}

Under the hood, JSX compiles to nested calls that build a plain JavaScript object describing the UI — the curly braces are how any JS expression gets embedded into that description, not a templating mini-language of their own.

The key on each <li> is not styling or an attribute for the browser; it is a hint React uses to match array items between one render and the next, so it can tell an insertion or reorder apart from every item changing.

HTML-like markup that compiles down to plain JavaScript function calls, with curly braces as the escape hatch back into JS.

Fragment (<>...</>)
Groups sibling elements without adding an extra node to the rendered DOM
Expression vs statement
Only expressions (ternaries, function calls, values) are valid inside { } — not if/for statements
key prop
Tells React which array item a rendered element corresponds to across re-renders
  • jsx
  • fragment
  • key
  • expressions

Component Composition

Composition means a component stays simple by delegating pieces of itself to other components, rather than growing new props and internal branches every time a new variant is needed. A Dialog that accepts a children prop for its body works for any content its caller wants to put there, without the Dialog ever needing to know what that content is.

This scales better than configuration through props: a Card with a footer slot handles arbitrarily different footers, while a Card with showPrimaryButton, showSecondaryButton, and showBadge flags only handles the combinations someone thought to add.

Starting a new file? See the Function Component Starter, Component with Children Starter, and Compound Component Starter in Starter Templates for ready-to-copy versions of these shapes — plus the Error Boundary Starter for the one case that still needs a class component.

Building a screen out of small, focused components instead of one large component configured through a growing pile of props.

children prop
The special prop holding whatever is nested between a component's opening and closing tags
Slot-like props
Passing a whole element (icon, header) as a prop instead of a boolean flag for every variant
Container / presentational split
Separating what data a component fetches from how that data is displayed
  • composition
  • children
  • component design

Props & State

Props & Prop Drilling

function OrderPage({ user }) {
  return <OrderSummary user={user} />;
}

function OrderSummary({ user }) {
  return <UserBadge user={user} />;
}

function UserBadge({ user }) {
  return <span>{user.name}</span>;
}

Only UserBadge actually reads user, but OrderPage and OrderSummary both have to accept and forward it just to pass it along — that’s prop drilling. It’s harmless for one or two hops, but once a value has to cross four or five components untouched, it’s usually the signal to reach for context or restructure the component tree instead.

Data flows down through props one direction only — which is simple, until the component that needs a value is many layers below the one that has it.

Props
Read-only data passed from a parent into a child component
Prop drilling
Passing a prop through components that don't use it themselves, just to reach one that does
Unidirectional data flow
Data flows down through props; a child never mutates its parent props directly
  • props
  • prop drilling
  • data flow

useState & State Updates

function Counter() {
  const [count, setCount] = useState(0);

  function handleIncrement() {
    setCount((prev) => prev + 1);
  }

  return <button onClick={handleIncrement}>Count: {count}</button>;
}

Passing a function to setCount instead of a plain value matters whenever the next value depends on the previous one — it always receives the latest state, even if several updates are queued before React re-renders.

Calling the setter never changes count immediately in the current render; it tells React to schedule a new render where count will hold the updated value, which is why reading count again right after calling setCount still shows the old number.

For a ready-to-copy component built on this pattern, see the Component with Local State Starter in Starter Templates.

The hook that gives a component memory across renders, plus the setter function that schedules a re-render rather than mutating a variable in place.

useState
Declares a piece of state and returns [value, setter]
Updater function
setCount(prev => prev + 1) avoids relying on a stale closed-over value
Re-render trigger
Calling the setter schedules a re-render; the change is not applied synchronously
  • usestate
  • state
  • hooks
  • re-render

Hooks

The Built-in Hooks at a Glance

Most hooks fall into a handful of families: state hooks (useState, useReducer) hold data across renders, effect hooks (useEffect, useLayoutEffect) synchronize with the outside world, and ref hooks (useRef) hold mutable values that don’t participate in rendering at all. useContext reads shared data, while useMemo and useCallback cache values and function references to avoid redoing work on every render.

A newer group exists mainly for concurrent rendering and server rendering: useTransition and useDeferredValue let an update be treated as non-urgent, useId generates ids that match between server and client, and useSyncExternalStore subscribes safely to state that lives outside React entirely, such as a browser API or a third-party store.

React's built-in hooks split cleanly into a few families, each covering a different job a class component used to handle with lifecycle methods.

State hooks
useState and useReducer hold values that persist across renders and trigger a re-render when changed
Effect hooks
useEffect and useLayoutEffect synchronize a component with something outside React
Concurrent hooks
useTransition and useDeferredValue mark updates as lower priority so the UI stays responsive
  • hooks
  • overview
  • react api

Rules of Hooks

function ShippingForm({ expedited }) {
  // Wrong: this hook call is skipped entirely on some renders
  if (expedited) {
    const [rushFee, setRushFee] = useState(25);
  }

  // Right: always call the hook, branch on the value instead
  const [rushFee, setRushFee] = useState(expedited ? 25 : 0);

  return <p>Rush fee: ${rushFee}</p>;
}

React has no idea a hook is named rushFee — it only knows it was the second useState call during this render. If a condition makes that call happen on some renders and not others, every hook after it shifts position and silently reads or writes the wrong slot of state.

That’s the entire reasoning behind the rule: call hooks only at the top level of a function component or another hook, in the same order, on every single render, no matter which branch the render takes.

Writing a new hook of your own? See the Custom Hook Starter and Data-Fetching Hook Starter in Starter Templates for a ready-to-copy shape.

Hooks are matched between renders purely by the order they're called in, which is why they can only ever be called from the top level of a component.

Call order
React matches hooks between renders by call order, not by variable name
Top level only
Never call hooks inside conditions, loops, or nested functions
Custom hooks
Functions starting with 'use' that call other hooks, following the same rules
  • hooks
  • rules of hooks
  • custom hooks

Effects & Refs

useEffect & Dependency Arrays

function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    const controller = new AbortController();
    fetchResults(query, controller.signal).then(setResults);
    return () => controller.abort();
  }, [query]);

  return <ResultsList items={results} />;
}

The effect only re-runs when query changes, since that’s the single value listed in its dependency array — an empty array would mean “run once on mount,” and omitting the array entirely would mean “run after every render.”

The cleanup function returned here cancels the in-flight request before the next effect run starts, which is what stops a slow response to an earlier query from overwriting the results of a newer one.

This exact fetch-cancel-cleanup shape is common enough to be worth its own starter — see the Data-Fetching Hook Starter in Starter Templates for a version wrapped up as a reusable hook.

Runs a side effect after the DOM commits, re-running only when a value in its dependency array actually changes.

Dependency array
The effect re-runs only when a listed value changes between renders
Cleanup function
The function an effect returns runs before the next run of that effect, and on unmount
Effects vs render
Effects run after the DOM commits, for syncing with things React doesn't manage — network, subscriptions, timers
  • useeffect
  • dependency array
  • cleanup
  • side effects

useRef & the DOM

function SearchInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} type="text" placeholder="Search..." />;
}

inputRef.current starts out null and is filled in by React with the actual DOM node once the <input> mounts, which is why reading it is only safe inside an effect or event handler, never during render itself.

Refs exist for the cases React’s declarative model doesn’t cover directly — focusing an element, measuring its size, or triggering an imperative method on a video player — anywhere the DOM node itself needs to be touched rather than described.

If a parent needs that same ref to reach a child’s DOM node, see the forwardRef Component Starter in Starter Templates.

A mutable box that survives across renders without causing one when it changes — the escape hatch for touching the DOM directly.

useRef
Holds a mutable .current value that persists across renders without triggering one
ref attribute
Attaching a ref to a JSX element gives access to its underlying DOM node
Refs vs state
Changing a ref does not cause a re-render; changing state does
  • useref
  • refs
  • dom

Context & Composition

useContext & Providers

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div className={`toolbar toolbar--${theme}`} />;
}

Toolbar reads theme without App ever passing it a prop, and without any component in between needing to know the value exists at all — that’s what makes context a fix for prop drilling on values many components need, like theme, locale, or the current user.

It isn’t free, though: every component reading a given context re-renders whenever that context’s value changes, so it fits widely-shared, slowly changing values better than anything that updates on every keystroke.

For the fuller pattern — context, provider component, and a guarded hook all together — see the Context + Provider + Hook Starter in Starter Templates.

Lets any descendant read a shared value directly, skipping however many components sit in between without passing it through props.

Provider
Supplies a value to every descendant that reads it with useContext
useContext
Reads the nearest matching Provider's value, skipping intermediate props
Re-render cost
Every consumer re-renders when the Provider's value changes, regardless of depth
  • usecontext
  • context
  • provider

Composition Over Inheritance

Coming from an object-oriented background, the instinct for sharing behavior between two similar components is often to put the shared parts in a base component and extend it. React deliberately doesn’t support that — there is no equivalent of a component subclassing another one.

Instead, shared behavior is pulled out sideways: a piece of stateful logic becomes a custom hook that several unrelated components can call, and shared structure becomes a component that others render inside of via children or a specific prop. Both avoid the rigid, single-parent relationship a class hierarchy would force, and either piece can be reused on its own without dragging the rest of a base class along with it.

A family of components sharing state implicitly, like Tabs and Tabs.Tab, is composition taken one step further — see the Compound Component Starter in Starter Templates for a ready-to-copy version.

React has no supported way for one component to extend another — shared behavior comes from combining components, not from a class hierarchy.

No component inheritance
React has no supported pattern for one component to extend another
Composition
Share behavior by combining components — children, props, custom hooks — not by extending a base component
Custom hooks over base classes
Extracting stateful logic into a hook reuses behavior without any class hierarchy
  • composition
  • inheritance
  • component design

Performance & Memoization

useMemo & useCallback

function ProductList({ products, onSelect }) {
  const sorted = useMemo(
    () => [...products].sort((a, b) => a.price - b.price),
    [products]
  );

  const handleSelect = useCallback((id) => onSelect(id), [onSelect]);

  return sorted.map((p) => (
    <ProductRow key={p.id} product={p} onSelect={handleSelect} />
  ));
}

sorted is only recomputed when products changes, instead of re-sorting on every render of ProductList — including renders triggered by something else entirely, like a sibling’s state changing.

useCallback does the same job for a function reference rather than a value: handleSelect stays the same function across renders as long as onSelect hasn’t changed, which matters specifically because ProductRow is wrapped in React.memo and would otherwise re-render on every pass.

Cache a computed value or a function reference across renders, so unrelated re-renders stop redoing work or invalidating a child that expects a stable prop.

useMemo
Recomputes a cached value only when one of its dependencies changes
useCallback
Returns a stable function reference across renders unless its dependencies change
When it matters
Mainly worth it for genuinely expensive work, or to avoid breaking a child's React.memo
  • usememo
  • usecallback
  • memoization
  • performance

React.memo & Re-render Triggers

A component re-renders whenever its own state changes, whenever its parent re-renders for any reason, or whenever a context it reads changes — parent re-rendering is the one that surprises people, since it happens regardless of whether the props passed down actually changed.

React.memo addresses exactly that middle case: it skips re-rendering a component if a shallow comparison of its new props matches the old ones. The common way this fails silently is a prop like onSelect={() => ...} or style={{ color: 'red' }} written inline in the parent — a new object or function is created on every render, so the shallow comparison never matches, and memo never actually helps until that prop is itself memoized.

A different lever entirely — not rendering less often, but not downloading the code until it’s needed — is code splitting; see the Lazy-Loaded Component Starter in Starter Templates.

A component re-renders far more often than intuition suggests — React.memo only helps once the actual trigger is understood.

Re-render triggers
A component re-renders when its own state changes, its parent re-renders, or a context it reads changes
React.memo
Skips re-rendering a component when its props are shallowly equal to the last render's
Shallow equality gotcha
A new inline object, array, or function prop breaks memo even when its contents look identical
  • react.memo
  • re-renders
  • performance
  • reconciliation
webcheet

↑↓ navigate↵ openesc close