Hooks
By the end of this lesson you'll know what hooks are, manage side effects with useEffect and its dependency array, reach into the DOM with useRef , share data with useContext , follow the Rules of Hooks, and package your own logic into a custom hook.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
A hook is a special function whose name starts with use that lets a function component "hook into" React features — like remembering state or running side effects. Before hooks (2019), only clunky class components could do these things; hooks brought them to simple functions.
You already know one hook: useState . This lesson adds the rest of the everyday toolkit. They all share two rules you'll meet at the end: call them at the top level of your component, and only from React functions .
1. Recap: useState gives a component memory
A plain function forgets everything between calls. useState fixes that: it stores a value across renders and gives you a setter to change it. Calling the setter tells React "this value changed — re-render me." The return value is always a pair: [value, setValue] . This box is read-only JSX; read every comment.
2. useEffect — running side effects
A side effect is anything that reaches outside React to touch the wider world: fetching data, starting a timer, subscribing to an event, or writing to localStorage . useEffect runs that code after React paints the screen, so it never blocks rendering. The second argument — the dependency array — controls when it runs, and an optional returned function is the cleanup .
useEffect(fn, []) — empty array. Runs once after the first render. Use for one-time setup (fetch on mount, add a global listener).
useEffect(fn, [a, b]) — runs after the first render and any time a or b changes value. Use to re-sync with changing props/state.
useEffect(fn) — no array. Runs after every render. Rarely what you want, and a common cause of infinite loops.
Cleanup: if your effect starts something (a timer, a subscription), return () => {' ... '} to stop it. React runs cleanup before the next effect and when the component unmounts.
Run this to see how the dependency array decides whether an effect re-runs — it uses the exact same Object.is comparison React does internally.
Your turn. The effect below should reconnect only when roomId changes, but the dependency array is empty. Fill in the blank so it watches the right variable, then run it and check the output.
3. useRef — DOM refs and mutable values
useRef hands you a small box, {' current: ... '} , that survives re-renders but, unlike state, does not cause one when you change it. Two everyday jobs: grab a real DOM node (to focus an input or scroll an element) via the ref={' ... '} attribute, and remember a value between renders without re-rendering (like a timer id).
Run this to feel the key difference: a ref is just an object you can mutate in place, and mutating it never triggers a render — which is exactly why it's safe for a "count the renders" counter that state could never do without looping.
4. useContext — shared data without prop drilling
Passing a value down through many layers of components just to reach a deep child is called prop drilling , and it's tedious. useContext lets a deeply nested component read a value provided by an ancestor directly. It's perfect for things many components need: the current theme, the logged-in user, or the chosen language.
5. The Rules of Hooks
Hooks rely on being called in the same order on every render — that's how React keeps each one matched to its stored value. Break the order and React loses track. Two rules guarantee a stable order:
1. Only call hooks at the top level. Never inside loops, conditions ( if ), or nested functions. If you need conditional behaviour, put the condition inside the hook, not around it.
2. Only call hooks from React functions — from a component, or from another custom hook (a function whose name starts with use ). Never from a plain helper function or an event handler.
Tip: the eslint-plugin-react-hooks lint plugin catches both mistakes (and missing dependencies) automatically.
6. Writing a custom hook
When two components need the same stateful logic, you don't copy-paste it — you extract it into a custom hook : a function named use... that calls other hooks and returns whatever you want. This is React's main way to reuse logic. Run the worked useCounter below; we fake useState with a closure so the logic runs in plain JS.
Q: What's the difference between useState and useRef ?
Both persist a value across renders, but changing useState re-renders the component, while changing a useRef does not. Use state for anything shown on screen; use a ref for behind-the-scenes values like a timer id or a DOM node.
After React has rendered and painted the screen. With [] it runs once; with dependencies it runs again whenever one of them changes; with no array it runs after every render.
Almost always an effect that calls setState but has a missing or always-changing dependency array, so it re-runs forever. Give it the correct array — often [] or the specific values it reads.
Yes. The use prefix is how React and its lint rules recognise a hook and enforce the Rules of Hooks. useCounter works; getCounter would not be treated as a hook.
No blanks this time — just a brief and a starter outline. Write a useToggle custom hook that flips a boolean. The fakeUseState helper is provided so it runs in our editor; build it, run it, and check your output against the expected lines in the comments.
Practice quiz
What is a React hook?
- A CSS class for styling components
- A type of class component
- A special function whose name starts with 'use' that lets a component use React features
- A way to import modules
Answer: A special function whose name starts with 'use' that lets a component use React features. Hooks are 'use...' functions that let function components hook into state, effects, and other React features.
What does useState return?
useState returns an array pair: the current value and a setter that triggers a re-render when called.
When does useEffect run relative to rendering?
- Before React renders
- Only on unmount
- During the render itself, synchronously
- After React renders and paints the screen
Answer: After React renders and paints the screen. useEffect runs after the render is committed and painted, so it never blocks rendering.
What does an empty dependency array, useEffect(fn, []), mean?
- Run after every render
- Run once after the first render (on mount)
- Never run
- Run only on unmount
Answer: Run once after the first render (on mount). An empty array has no dependencies to change, so after the first run the effect never re-runs.
What does useEffect with NO dependency array do?
- Runs after every render
- Runs once on mount
- Never runs
- Runs only when state changes
Answer: Runs after every render. With no array React cannot compare anything, so the effect runs after every render — a common cause of infinite loops.
How do you clean up a subscription or timer in useEffect?
- Call cleanup() at the end of the effect
- Use a separate useCleanup hook
- Return a function from the effect
- Set a cleanup flag in state
Answer: Return a function from the effect. The function you return from an effect is its cleanup; React runs it before the next effect and on unmount.
What is the key difference between useRef and useState?
- useRef can only hold DOM nodes
- Changing a useRef does NOT trigger a re-render; changing state does
- useRef triggers a re-render but useState does not
- There is no difference
Answer: Changing a useRef does NOT trigger a re-render; changing state does. Both persist across renders, but mutating ref.current never re-renders, while setting state does.
Which is the FIRST Rule of Hooks?
- Only call hooks inside loops
- Always call hooks inside an if statement
- Call hooks only in event handlers
- Only call hooks at the top level — never inside loops, conditions, or nested functions
Answer: Only call hooks at the top level — never inside loops, conditions, or nested functions. Hooks must be called at the top level so they run in the same order every render, keeping each matched to its stored value.
From where may you call a hook?
- Any plain JavaScript function
- Only from a React component or another custom hook
- Only from event handlers
- Only from the top of a file
Answer: Only from a React component or another custom hook. Hooks may only be called from React function components or from custom hooks (functions named use...).
What does useContext do?
- Creates a new component
- Replaces useState entirely
- Reads a value from a Provider ancestor without prop drilling
- Caches network requests
Answer: Reads a value from a Provider ancestor without prop drilling. useContext lets a deeply nested component read a value provided by an ancestor directly, avoiding prop drilling.