Checkpoint: Build with Hooks
You've learned useState , useEffect , useRef , useReducer and useContext — time to put them together. No new theory in this lesson. Instead you'll build a small app that requires several hooks working as a team, then take a quiz to lock in what each hook is really for. Treat anything you get wrong as a friendly nudge to revisit that earlier lesson.
Learn Checkpoint: Build with Hooks in our free React course — a beginner-friendly interactive lesson with runnable examples, a practice exercise and a quick…
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.
🏗️ Build Challenge: A Filterable Todo List
Build a Todo app that combines several hooks. In a real component you'd wire it up like this — your requirements:
The starter below models the trickiest pieces (the reducer, the counter, the filter). Fill in the blanks so the output matches.
Here's how the same logic looks wired into a real component, using all five hooks together:
Why each hook? useReducer keeps the add/toggle transitions in one tidy place. useState handles the flat filter value. useRef reaches the input DOM node without re-rendering on every keystroke. Two useEffect s handle the side effects: one focuses on mount, one persists on change. And itemsLeft / visible are just derived during render — no extra state to keep in sync.
📝 Checkpoint Quiz
1) You need a value that survives re-renders but should not trigger a re-render when it changes (e.g. a previous-value tracker or an interval id). Which hook?
A) useState B) useRef C) useReducer D) useContext
B) useRef. Mutating ref.current doesn't schedule a render. useState would re-render on every change.
2) Predict the output. What logs, and how many times?
"run" logs once , after the first render. An empty dependency array means "run on mount only." (In React 18 Strict Mode during development it runs twice on purpose — to surface missing cleanup.)
3) Two components both read useContext(ThemeContext) . The provider's value changes from "light" to "dark". What happens?
Both consumers re-render with the new value. Every component that calls useContext for that context re-renders when the provider's value changes — no props needed.
0. Two increments take 5 → 7, then "reset" returns {' '} . A reducer is a pure function: same state + action → same next state.
5) Why does this effect cause an infinite loop, and how do you fix it?
There's no dependency array , so the effect runs after every render. It calls setCount , which re-renders, which runs the effect again — forever. Fix it by giving it the right deps (e.g. [] to run once) or removing the state update from the effect.
6) You have a counter ( number ) and a settings object with five fields and several transition rules. Which two hooks fit best, respectively?
useState for the simple counter; useReducer for the multi-field settings with rules. Rule of thumb: flat, independent values → useState; related fields with structured transitions → useReducer.
1) In the Todo app, why is itemsLeft NOT its own piece of state?
Because it's fully derivable from todos . Computing it during render keeps it always in sync; storing it would create a second source of truth to maintain.
2) Which hook handles "save todos to localStorage whenever they change"?
useEffect with [todos] as the dependency — a side effect that re-runs each time the list changes.
Practice quiz
You need a value that survives re-renders but must NOT trigger a re-render when it changes (e.g. an interval id). Which hook?
- useState
- useReducer
- useRef
- useContext
Answer: useRef. Mutating ref.current does not schedule a render. useState would re-render on every change.
What does an empty dependency array, as in useEffect(fn, []), mean?
- Run only on mount (and clean up on unmount)
- Run after every render
- Never run the effect
- Run only when state changes
Answer: Run only on mount (and clean up on unmount). An empty dependency array means the effect runs once on mount. In Strict Mode dev it runs twice on purpose to surface missing cleanup.
Two components both call useContext(ThemeContext). The provider value changes from 'light' to 'dark'. What happens?
- Only the provider re-renders
- Nothing until you pass new props
- You must call forceUpdate manually
- Both consumers re-render with the new value
Answer: Both consumers re-render with the new value. Every component that reads a context via useContext re-renders when that provider's value changes, with no props needed.
Why does useEffect(() => setCount(count + 1)) with no dependency array cause an infinite loop?
- setCount is asynchronous
- The effect runs after every render, and updating state triggers another render
- count is undefined
- useEffect cannot call setters
Answer: The effect runs after every render, and updating state triggers another render. With no deps the effect runs after every render; it sets state, which re-renders, which runs the effect again, forever. Add the right deps or move the update out.
A reducer is best described as what kind of function?
- A pure function: (state, action) maps to the next state
- An async function that fetches data
- A function that mutates state in place
- A React hook itself
Answer: A pure function: (state, action) maps to the next state. A reducer is pure: given the same state and action it always returns the same next state, with no mutation or side effects.
You have a flat counter (a number) and a settings object with five fields plus transition rules. Which hooks fit best, respectively?
- useReducer for the counter, useState for settings
- useRef for both
- useState for the counter, useReducer for settings
- useContext for both
Answer: useState for the counter, useReducer for settings. Flat, independent values fit useState; related fields with structured transitions fit useReducer.
What is the role of useEffect in 'save todos to localStorage whenever they change'?
- It is the wrong tool; use useRef
A side effect like persisting belongs in useEffect with [todos] so it re-runs each time the list changes.
In the Todo app, why is itemsLeft NOT stored in its own useState?
- Because numbers cannot be state
- Because useState is slower than useRef
- Because it must live in Context
- Because it is fully derivable from todos, so computing it during render keeps it in sync
Answer: Because it is fully derivable from todos, so computing it during render keeps it in sync. Derived values should be computed during render. Storing them creates a second source of truth you must keep in sync.
What does useRef give you beyond a mutable box that survives renders?
- Automatic re-rendering when .current changes
- A way to access and hold a DOM node via the ref attribute
- Global state shared across components
- A replacement for useReducer
Answer: A way to access and hold a DOM node via the ref attribute. useRef both holds a mutable value across renders without re-rendering and can be attached to a DOM element to reach the underlying node.
Which hook is the cleanest fit for sharing a value like the current user or theme down a deep tree without prop drilling?
- useRef
- useReducer
- useContext
- useEffect
Answer: useContext. useContext lets any descendant read a provided value directly, avoiding passing props through every intermediate component.