React Hooks Vocabulary

23 React hooks terms in plain English — what each one means, a code example, and the gotcha worth knowing.

Last reviewed:

Sections

Core concepts

hook

A function that lets a functional component "hook into" React features like state and lifecycle, without writing a class.

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

💡 All built-in hooks start with "use" — it's also a naming convention your own custom hooks must follow.

custom hook

A function you write yourself, starting with "use", that composes other hooks to share stateful logic between components.

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);
  useEffect(() => {
    const onResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);
  return width;
}

💡 The main way to share logic (not markup) between components — the hooks-era replacement for higher-order components.

render

The process of React calling your component function to figure out what the UI should look like right now.

// Every call to setCount triggers a re-render of Counter

💡 A render doesn't always mean the DOM changes — React diffs the result and only touches the DOM where needed.

controlled component

A form element whose value is driven entirely by React state, not by the DOM itself.

<input value={name} onChange={e => setName(e.target.value)} />

💡 The opposite is an "uncontrolled component", which reads its value via a ref instead of state.

State

useState

Adds a piece of local state to a component. Returns the current value and a setter function.

const [isOpen, setIsOpen] = useState(false);

💡 Calling the setter schedules a re-render — it doesn't update the variable "in place" synchronously.

useReducer

An alternative to useState for more complex state logic — updates go through a reducer function instead of direct setters.

const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'increment' });

💡 Reach for this when the next state depends on several related fields, or update logic gets hard to follow with useState alone.

lifting state up

Moving state from a child component to their shared parent, so multiple children can read and update the same value.

// Parent owns the state, passes value + setter down as props
<TabList active={active} onSelect={setActive} />

💡 The standard React pattern before reaching for context or a state library.

stale closure

A bug where a function captured an old value of state or props and keeps using it, even after the value has changed.

useEffect(() => {
  const id = setInterval(() => console.log(count), 1000); // always logs the count from render #1
  return () => clearInterval(id);
}, []); // missing "count" in the dependency array causes this

💡 The #1 cause of "why does my callback see an old value" bugs — almost always a missing dependency.

Effects & lifecycle

useEffect

Runs side effects (data fetching, subscriptions, manually changing the DOM) after React has committed the render to the screen.

useEffect(() => {
  document.title = `${count} clicks`;
}, [count]);

💡 Runs after every render by default — the dependency array controls when it re-runs.

dependency array

The array passed as the second argument to useEffect/useMemo/useCallback, telling React when to re-run the effect or recompute the value.

useEffect(() => { fetchUser(id); }, [id]); // re-runs only when id changes

💡 An empty array [] means "run once on mount"; omitting it entirely means "run after every render".

cleanup function

The function an effect can return, which React runs before the effect re-runs and when the component unmounts.

useEffect(() => {
  const sub = source.subscribe(handler);
  return () => sub.unsubscribe(); // cleanup
}, [source]);

💡 Forgetting cleanup is the classic cause of memory leaks and "setState after unmount" warnings.

mount / unmount

Mount is when a component is first added to the DOM; unmount is when it's removed. Hooks let you approximate these with an empty dependency array.

useEffect(() => {
  console.log('mounted');
  return () => console.log('unmounted');
}, []);

💡 React doesn't guarantee exactly-once mount in Strict Mode during development — effects can run twice on purpose to surface bugs.

useLayoutEffect

Like useEffect, but fires synchronously after DOM mutations and before the browser paints — for measurements that must happen before the user sees a flicker.

useLayoutEffect(() => {
  const { height } = ref.current.getBoundingClientRect();
  setHeight(height);
}, []);

💡 Blocks the browser paint until it finishes — use useEffect unless you specifically need to avoid visual flicker.

Performance

useMemo

Caches the result of an expensive calculation and only recomputes it when one of its dependencies changes.

const sorted = useMemo(() => items.slice().sort(compare), [items]);

💡 Not free — the memoisation itself has a cost. Only worth it for genuinely expensive computations.

useCallback

Returns a memoised version of a function that only changes if one of its dependencies changes — keeps the same function reference between renders.

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

💡 Mostly useful when passing callbacks to memoised child components (React.memo) that would otherwise re-render on every new function reference.

React.memo

A higher-order component that skips re-rendering a component if its props haven't changed (shallow comparison).

const Row = React.memo(function Row({ item }) {
  return <li>{item.name}</li>;
});

💡 Only useful if the component actually re-renders often with the same props — profile before reaching for it.

unnecessary re-render

A component re-rendering even though nothing the user can see actually needs to change — a common source of sluggish UI.

// Every keystroke in an unrelated sibling re-renders this whole list

💡 Fixed with React.memo, useMemo/useCallback, or restructuring state so it lives closer to where it's used.

Refs

useRef

Creates a mutable value that persists across renders without causing a re-render when it changes — commonly used to reference a DOM node.

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

💡 Unlike state, updating a ref's .current does not trigger a re-render — use it for values React itself doesn't need to react to.

forwardRef

Lets a component pass a ref it receives through to one of its own children, usually a DOM element.

const FancyInput = forwardRef((props, ref) => <input ref={ref} {...props} />);

💡 Needed because refs aren't regular props — they don't automatically pass through a custom component.

imperative handle

Customizes what a parent sees when it holds a ref to a child component, via useImperativeHandle, instead of exposing the raw DOM node.

useImperativeHandle(ref, () => ({
  focus: () => inputRef.current.focus(),
}));

💡 An escape hatch for imperative APIs (focus, scroll, play) — most components should stay declarative and avoid this.

Rules & context

rules of hooks

Two constraints every hook call must follow: only call hooks at the top level (never in loops/conditions), and only call them from React functions.

// ❌ Wrong — conditional hook call
if (isLoggedIn) { useEffect(() => {...}); }
// ✅ Correct — condition inside the hook
useEffect(() => { if (isLoggedIn) {...} }, [isLoggedIn]);

💡 React relies on hooks being called in the same order every render — breaking this rule causes hard-to-debug state corruption.

useContext

Reads a value from the nearest matching Context.Provider above the component in the tree, without prop-drilling it through every level.

const theme = useContext(ThemeContext);

💡 Any component using useContext re-renders whenever that context value changes — split large contexts to avoid over-rendering.

prop drilling

Passing a value down through many layers of components that don't use it themselves, just to get it to a deeply nested child.

<A user={user}><B user={user}><C user={user}><D user={user} /></C></B></A>

💡 The problem useContext (or a state library) is designed to solve.

English phrases engineers use

  • "That bug is a stale closure — you're missing a dependency in the effect."
  • "Don't call hooks conditionally — that breaks the rules of hooks."
  • "We're lifting state up so both tabs can share the same selection."
  • "This component is re-rendering way too often — let's wrap it in React.memo."
  • "Use a ref here, not state — we don't need a re-render for this."
  • "We ended up prop drilling five levels deep — time for context."