Chapter 31 — React Hooks in Practice
Section 1 — What hooks are
Hooks are functions that let a function component remember state, run side effects, and share logic, without writing a class component. That definition is worth unpacking slowly, because hooks shows up in almost every React screen you will build. When you can say in plain words what hooks does, you can decide where it belongs, what data it needs, and what should happen when that data changes. They give function components the full power of React, while keeping the code as plain functions that are easy to read and test.
Working with hooks usually follows a small, repeatable pattern. First, identify what the component must remember or do. Next, call the matching hook at the top of the component. Finally, use the value or function the hook returns. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [count, setCount] = useState(0);
Read the example from the top down. This one line gives the component memory: count holds the value, and setCount changes it when something happens. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with hooks is calling a hook inside a loop or condition, which changes the order hooks run in. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: In plain words, what does a hook let a function component do?
Section 2 — useState in depth
UseState in depth means understanding that its initial value is used once, its setter schedules a render, and its value never changes by direct assignment. That definition is worth unpacking slowly, because useState in depth shows up in almost every React screen you will build. When you can say in plain words what useState in depth does, you can decide where it belongs, what data it needs, and what should happen when that data changes. This deeper view prevents the most common state surprises, because you stop expecting the variable to change the moment you call the setter.
Working with useState in depth usually follows a small, repeatable pattern. First, call useState with a sensible starting value. Next, read the state value during rendering. Finally, call the setter when an event should change it. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [name, setName] = useState("Ada");
Read the example from the top down. Name starts as Ada, stays Ada until setName is called, and then the component renders again with the new name. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useState in depth is reading a state value immediately after calling its setter and expecting the new value already. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why does a state value not change immediately after you call its setter?
Section 3 — The Rules of Hooks
The Rules of Hooks say to call hooks only at the top level of a component or a custom hook, and never inside conditions, loops, or nested functions. That definition is worth unpacking slowly, because the Rules of Hooks shows up in almost every React screen you will build. When you can say in plain words what the Rules of Hooks does, you can decide where it belongs, what data it needs, and what should happen when that data changes. React relies on the order of hook calls to match each call with its stored value, so changing that order would mix up the values.
Working with the Rules of Hooks usually follows a small, repeatable pattern. First, place every hook call at the top of the component. Next, keep the same hooks in the same order on every render. Finally, move conditional logic inside the hook result, not around the hook. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [a, setA] = useState(0);
const [b, setB] = useState("");
Read the example from the top down. Both calls run on every render in the same order, so React can always tell which stored value belongs to which call. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with the Rules of Hooks is wrapping a useState call in an if statement that sometimes runs and sometimes does not. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What are the two Rules of Hooks in plain words?
Section 4 — useEffect basics
UseEffect is a hook for running a side effect after rendering, such as fetching data, setting up a subscription, or updating the document title. That definition is worth unpacking slowly, because useEffect shows up in almost every React screen you will build. When you can say in plain words what useEffect does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It separates outside work from rendering, so rendering stays a pure calculation while effects handle the messy outside world.
Working with useEffect usually follows a small, repeatable pattern. First, write the effect function inside useEffect. Next, list the values the effect depends on. Finally, let React run it after the component renders. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
Read the example from the top down. The title updates after rendering whenever count changes, and the dependency list tells React exactly when to run it again. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useEffect is doing outside work directly during rendering instead of inside an effect or handler. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What kind of work belongs inside useEffect?
Section 5 — Effect dependencies
An effect dependency list names every value from the component that the effect reads, so React knows when to run the effect again. That definition is worth unpacking slowly, because effect dependencies shows up in almost every React screen you will build. When you can say in plain words what effect dependencies does, you can decide where it belongs, what data it needs, and what should happen when that data changes. A correct dependency list keeps the effect in sync: it runs when something it uses changes, and skips running when nothing relevant changed.
Working with effect dependencies usually follows a small, repeatable pattern. First, list every value the effect reads. Next, add those values to the dependency array. Finally, check that removing a value would make the effect stale. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
console.log(name);
}, [name]);
Read the example from the top down. Because name is listed, the effect logs again whenever name changes, and it never logs a stale name from an old render. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with effect dependencies is leaving a used value out of the dependency list, so the effect keeps using an old value. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you decide what belongs in a dependency list?
Section 6 — Cleanup in effects
Effect cleanup is a function an effect returns, which React runs before the effect runs again and when the component unmounts. That definition is worth unpacking slowly, because effect cleanup shows up in almost every React screen you will build. When you can say in plain words what effect cleanup does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Cleanup prevents leaks and stale work, such as a subscription that keeps firing after the component that wanted it has gone.
Working with effect cleanup usually follows a small, repeatable pattern. First, set up the outside resource inside the effect. Next, return a function that undoes that setup. Finally, let React run the cleanup at the right moment. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
Read the example from the top down. The interval starts when the effect runs, and the returned function clears it before a new effect or unmount, so no stray timer remains. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with effect cleanup is starting a timer or subscription in an effect without returning any cleanup. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: When does React run an effect cleanup function?
Section 7 — Effects versus event handlers
An event handler runs because a user did something specific, while an effect runs because rendering happened and something needs syncing. That definition is worth unpacking slowly, because effects versus event handlers shows up in almost every React screen you will build. When you can say in plain words what effects versus event handlers does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Choosing the right one keeps logic in the right place: user actions belong in handlers, and syncing with an outside system belongs in effects.
Working with effects versus event handlers usually follows a small, repeatable pattern. First, ask whether a user action caused the work. Next, if yes, put it in the handler. Finally, if the work is syncing after a render, put it in an effect. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function SaveButton({ onSave }) {
return <button onClick={onSave}>Save</button>;
}
Read the example from the top down. Saving is a direct response to a click, so it belongs in the handler, not in an effect that watches for a change. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with effects versus event handlers is putting a save triggered by a click into an effect, which then runs at confusing times. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you decide between an event handler and an effect?
Section 8 — Fetching data with an effect
Fetching data with an effect means starting a request after rendering, storing the result in state, and rendering loading, error, or data states. That definition is worth unpacking slowly, because fetching data with an effect shows up in almost every React screen you will build. When you can say in plain words what fetching data with an effect does, you can decide where it belongs, what data it needs, and what should happen when that data changes. This pattern keeps the screen responsive: the component renders immediately with a loading message, then updates when data arrives.
Working with fetching data with an effect usually follows a small, repeatable pattern. First, start with loading state true. Next, fetch inside an effect and store the result in state. Finally, render loading, error, or data depending on state. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
fetch("/api/items").then(r => r.json()).then(setItems);
}, []);
Read the example from the top down. The empty dependency list means the fetch runs once after the first render, and setItems triggers a re-render with the data. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with fetching data with an effect is fetching during rendering, which can start a new request on every render and loop. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why fetch inside an effect rather than during rendering?
Section 9 — Loading and error states for fetching
Loading and error states are state values that describe whether a request is in progress, failed, or succeeded, so the screen can say so honestly. That definition is worth unpacking slowly, because loading and error states shows up in almost every React screen you will build. When you can say in plain words what loading and error states does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Users need to know what is happening: a silent blank screen looks broken, while a clear loading or error message builds trust.
Working with loading and error states usually follows a small, repeatable pattern. First, track loading, error, and data as separate state. Next, set loading true before the request and false after. Finally, render a message for loading and for error before rendering data. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Read the example from the top down. These two values together describe the whole request lifecycle, so the markup can respond to each stage correctly. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with loading and error states is rendering data before checking loading or error, which can crash on missing data. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What three states should a data fetch display?
Section 10 — useRef basics
UseRef returns a mutable object with a current property that persists across renders but does not trigger a re-render when it changes. That definition is worth unpacking slowly, because useRef shows up in almost every React screen you will build. When you can say in plain words what useRef does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It is for values you need to keep without displaying them, such as a timer id, a previous value, or a reference to a DOM element.
Working with useRef usually follows a small, repeatable pattern. First, call useRef with an initial value. Next, read or write ref dot current. Finally, do not expect changing it to update the screen. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const inputRef = useRef(null);
<input ref={inputRef} />;
Read the example from the top down. InputRef points at the input element once React attaches it, and you can use that reference in a handler later. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useRef is using a ref for a value the screen must display, then wondering why the display never updates. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What does useRef return, and what does changing it not do?
Section 11 — Refs versus state
State is for values that should update the screen when they change, while refs are for values that should persist silently without re-rendering. That definition is worth unpacking slowly, because refs versus state shows up in almost every React screen you will build. When you can say in plain words what refs versus state does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Choosing correctly avoids both stale screens and wasted renders: display values need state, and bookkeeping values need refs.
Working with refs versus state usually follows a small, repeatable pattern. First, ask whether changing the value should update the display. Next, if yes, use state. Finally, if no, and it must survive renders, use a ref. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const timerRef = useRef(null);
const [seconds, setSeconds] = useState(0);
Read the example from the top down. Seconds is state because the screen shows it, while the timer id is a ref because only the code needs it. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with refs versus state is storing a timer id in state, which causes an extra render every time it is set. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you choose between a ref and state?
Section 12 — Focusing an input with a ref
Focusing an input with a ref means attaching a ref to the input element and calling focus on its current value from a handler or effect. That definition is worth unpacking slowly, because focusing with a ref shows up in almost every React screen you will build. When you can say in plain words what focusing with a ref does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It is a direct, honest use of a ref: you need the real DOM element for one imperative action, not a value to display.
Working with focusing with a ref usually follows a small, repeatable pattern. First, create a ref with useRef. Next, attach it to the input with the ref attribute. Finally, call ref dot current dot focus when needed. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function FocusForm() {
const ref = useRef(null);
return <button onClick={() => ref.current.focus()}>Focus</button>;
}
Read the example from the top down. Clicking the button asks the referenced element to focus itself, which is an action on the DOM, not a change to displayed data. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with focusing with a ref is trying to focus an element before it exists, when ref dot current is still null. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you focus an input using a ref?
Section 13 — Storing previous values and timers in refs
A ref can store a previous value or a timer id across renders, because its current property survives while the component re-renders around it. That definition is worth unpacking slowly, because refs for previous values shows up in almost every React screen you will build. When you can say in plain words what refs for previous values does, you can decide where it belongs, what data it needs, and what should happen when that data changes. This is useful for comparing now with before, or for clearing a timer later, without those bookkeeping values causing extra renders.
Working with refs for previous values usually follows a small, repeatable pattern. First, create a ref for the bookkeeping value. Next, update ref dot current in an effect or handler. Finally, read it when you need the old value or the timer id. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const prevRef = useRef(0);
useEffect(() => { prevRef.current = count; }, [count]);
Read the example from the top down. After each count change, the effect stores the new count as the previous value for the next comparison. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with refs for previous values is storing a previous value in state, which triggers an extra render just to remember something. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why use a ref for a previous value rather than state?
Section 14 — useMemo basics
UseMemo caches the result of an expensive calculation and only recomputes it when one of its dependencies changes. That definition is worth unpacking slowly, because useMemo shows up in almost every React screen you will build. When you can say in plain words what useMemo does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It prevents wasted work on renders where the inputs are unchanged, which matters when a calculation is genuinely slow.
Working with useMemo usually follows a small, repeatable pattern. First, identify an expensive calculation. Next, wrap it in useMemo with its dependencies. Finally, use the returned cached value in the markup. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const total = useMemo(() => items.reduce((s, i) => s + i.price, 0), [items]);
Read the example from the top down. Total is recomputed only when items changes, not on every unrelated render of the component. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useMemo is wrapping every cheap calculation in useMemo, which adds complexity without any real saving. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What does useMemo cache, and when does it recompute?
Section 15 — useCallback basics
UseCallback caches a function definition and only creates a new function when its dependencies change. That definition is worth unpacking slowly, because useCallback shows up in almost every React screen you will build. When you can say in plain words what useCallback does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It matters when a function is passed to a memoised child or used as an effect dependency, because a new function each render would defeat that caching.
Working with useCallback usually follows a small, repeatable pattern. First, identify a function passed to a child or effect. Next, wrap it in useCallback with its dependencies. Finally, pass the cached function instead of a fresh one. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const handleSave = useCallback(() => save(data), [data]);
Read the example from the top down. HandleSave stays the same function while data is unchanged, so a child that depends on its identity does not re-render needlessly. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useCallback is wrapping every handler in useCallback by default, even when nothing depends on the function identity. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: When does caching a function with useCallback actually help?
Section 16 — When memoisation helps and hurts
Memoisation helps when a calculation or function identity is expensive or drives child renders, and hurts when it adds complexity for a trivial saving. That definition is worth unpacking slowly, because memoisation trade-offs shows up in almost every React screen you will build. When you can say in plain words what memoisation trade-offs does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The cost of memoisation is real: dependency lists to maintain, code to read, and bugs when a dependency is missed.
Working with memoisation trade-offs usually follows a small, repeatable pattern. First, measure or reason about whether the work is expensive. Next, memoise only the expensive or identity-sensitive cases. Finally, leave cheap calculations plain and direct. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const doubled = count * 2; // cheap, no memo needed
Read the example from the top down. Doubling a number costs almost nothing, so memoising it would add code and risk for no benefit at all. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with memoisation trade-offs is memoising everything in advance, which makes the code harder to read and easier to break. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you decide whether to memoise something?
Section 17 — useContext basics
UseContext lets a component read a value shared through context, without that value being passed down as props through every layer. That definition is worth unpacking slowly, because useContext shows up in almost every React screen you will build. When you can say in plain words what useContext does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It is the answer to deep prop drilling for values many components need, such as the current theme or the signed-in user.
Working with useContext usually follows a small, repeatable pattern. First, create a context with a default value. Next, provide a value higher in the tree. Finally, read it in a descendant with useContext. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const ThemeContext = createContext("light");
const theme = useContext(ThemeContext);
Read the example from the top down. Any descendant can read the theme directly, without intermediate components forwarding it as a prop. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useContext is using context for every value, even one needed by only a parent and its direct child. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What problem does useContext solve?
Section 18 — Creating and providing context
Providing context means wrapping part of the tree in a Provider component with a value, which all descendants can then read. That definition is worth unpacking slowly, because providing context shows up in almost every React screen you will build. When you can say in plain words what providing context does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The Provider sets the shared value in one place, and descendants read it, so the value can change in one place and reach many components.
Working with providing context usually follows a small, repeatable pattern. First, create the context object. Next, wrap the relevant subtree in its Provider. Finally, pass the shared value to the Provider. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function App() {
return <ThemeContext.Provider value="dark"><Page /></ThemeContext.Provider>;
}
Read the example from the top down. Page and everything inside it can read dark as the theme, without Page receiving theme as a prop at all. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with providing context is providing a new object value on every render, which makes every consumer re-render even when nothing meaningful changed. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Where does a context value get set?
Section 19 — Consuming context and avoiding prop drilling
Consuming context means calling useContext in the component that needs the value, which subscribes it to that context. That definition is worth unpacking slowly, because consuming context shows up in almost every React screen you will build. When you can say in plain words what consuming context does, you can decide where it belongs, what data it needs, and what should happen when that data changes. A consumer updates when the provided value changes, and it stays independent of intermediate components that do not care about the value.
Working with consuming context usually follows a small, repeatable pattern. First, call useContext with the context object. Next, use the returned value in the component. Finally, keep the component working when no Provider wraps it, via a sensible default. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function Title() {
const theme = useContext(ThemeContext);
return <h1>{theme}</h1>;
}
Read the example from the top down. Title reads the theme directly, so adding layers between the Provider and Title requires no changes to those layers. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with consuming context is reading context in a component that is not wrapped by a Provider, and being surprised by the default value. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How does a component consume a context value?
Section 20 — useReducer basics
UseReducer manages complex state through a reducer function: you dispatch an action, and the reducer returns the next state from the current state and that action. That definition is worth unpacking slowly, because useReducer shows up in almost every React screen you will build. When you can say in plain words what useReducer does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It suits state with several related values or several ways to change, where many separate setters would become tangled.
Working with useReducer usually follows a small, repeatable pattern. First, write a reducer that takes state and action and returns new state. Next, call useReducer with the reducer and initial state. Finally, dispatch actions instead of calling individual setters. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [state, dispatch] = useReducer(reducer, { count: 0 });
Read the example from the top down. State holds the whole shape, and dispatch is the single way to request a change, which keeps updates in one place. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useReducer is writing a reducer that mutates state instead of returning a new state object. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What are the three parts of useReducer: reducer, state, and dispatch?
Section 21 — Reducer actions
A reducer action is a plain object, usually with a type string describing what happened, plus any data the update needs. That definition is worth unpacking slowly, because reducer actions shows up in almost every React screen you will build. When you can say in plain words what reducer actions does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Actions make updates readable and traceable: you can log them, list them, and see exactly which kind of change was requested.
Working with reducer actions usually follows a small, repeatable pattern. First, define a type string for each kind of change. Next, dispatch an object with that type and its data. Finally, handle each type in the reducer with a case. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
dispatch({ type: "increment" });
Read the example from the top down. This action says only that an increment was requested; the reducer decides how the state changes in response. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with reducer actions is putting the update logic in the dispatch call instead of in the reducer, which scatters state rules everywhere. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What does an action object usually contain?
Section 22 — useState versus useReducer
Use useState for simple, independent values, and useReducer when several values change together or updates follow clear rules. That definition is worth unpacking slowly, because useState versus useReducer shows up in almost every React screen you will build. When you can say in plain words what useState versus useReducer does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The choice is about complexity: a reducer pays off when updates interact, but it is overkill for a single boolean or string.
Working with useState versus useReducer usually follows a small, repeatable pattern. First, ask how many values change together. Next, ask how many ways the state can change. Finally, choose the reducer when both answers are several, otherwise useState. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [open, setOpen] = useState(false); // simple
// reducer for a multi-step form
Read the example from the top down. A single toggle is clearer with useState, while a form with steps, errors, and data is clearer with a reducer. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useState versus useReducer is reaching for a reducer for every tiny piece of state, which adds boilerplate without benefit. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you choose between useState and useReducer?
Section 23 — Custom hooks basics
A custom hook is a function whose name starts with use and which calls other hooks inside it, extracting reusable stateful logic. That definition is worth unpacking slowly, because custom hooks shows up in almost every React screen you will build. When you can say in plain words what custom hooks does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It lets you share logic, not just markup: several components can use the same behaviour while each keeps its own independent state.
Working with custom hooks usually follows a small, repeatable pattern. First, write a function starting with use. Next, call hooks inside it at the top level. Finally, return the values and functions callers need. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function useToggle(initial = false) {
const [on, setOn] = useState(initial);
return [on, () => setOn(!on)];
}
Read the example from the top down. UseToggle packages a boolean and its flip function, so any component can add toggle behaviour in one line. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with custom hooks is naming a helper useSomething but not calling any hook inside it, which makes the name misleading. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What makes a function a custom hook?
Section 24 — Extracting a custom hook
Extracting a custom hook means noticing the same stateful logic in two components and moving it into one shared hook function. That definition is worth unpacking slowly, because extracting a custom hook shows up in almost every React screen you will build. When you can say in plain words what extracting a custom hook does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Extraction removes duplication while keeping each component state independent, because each call to the hook gets its own state.
Working with extracting a custom hook usually follows a small, repeatable pattern. First, find logic repeated in more than one component. Next, move the hooks and handlers into a use function. Finally, replace the repeated code in each component with a call to the hook. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function useCounter() {
const [n, setN] = useState(0);
return [n, () => setN(n + 1)];
}
Read the example from the top down. Two components calling useCounter each get their own count, which is usually what you want from shared logic. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with extracting a custom hook is expecting two components that call the same hook to share one state value. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Do two components using the same custom hook share state?
Section 25 — Custom hook for form input
A custom hook for form input can package a value, its change handler, and a reset function, so each field needs only one hook call. That definition is worth unpacking slowly, because custom hook for form input shows up in almost every React screen you will build. When you can say in plain words what custom hook for form input does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It removes the repetitive trio of state, value, and onChange that every controlled field otherwise repeats.
Working with custom hook for form input usually follows a small, repeatable pattern. First, store the field value in state inside the hook. Next, return the value and an onChange handler. Finally, spread or assign those props onto the input. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function useField(initial = "") {
const [value, setValue] = useState(initial);
return { value, onChange: e => setValue(e.target.value) };
}
Read the example from the top down. A caller can write const name equals useField, then pass its value and handler to an input, with far less repetition. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with custom hook for form input is hiding validation or formatting inside the hook without documenting it, so callers are surprised by the behaviour. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What does a form-input hook return?
Section 26 — Custom hook for fetching data
A custom hook for fetching can package loading, error, and data state plus the effect that requests the data, behind one call. That definition is worth unpacking slowly, because custom hook for fetching data shows up in almost every React screen you will build. When you can say in plain words what custom hook for fetching data does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It turns a repeated fetch pattern into a single reusable line, while each caller still gets its own request state.
Working with custom hook for fetching data usually follows a small, repeatable pattern. First, take the request address as an argument. Next, manage loading, error, and data inside the hook. Finally, return those three values to the caller. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function useData(url) {
const [data, setData] = useState(null);
useEffect(() => { fetch(url).then(r => r.json()).then(setData); }, [url]);
return data;
}
Read the example from the top down. A component can call useData with its address and receive data, without repeating the fetch and state code. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with custom hook for fetching data is forgetting to include the address in the effect dependencies, so changing the address does not refetch. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What should a data-fetching hook return to its caller?
Section 27 — Custom hook rules and naming
Custom hooks follow the same rules as built-in hooks, and their names must start with use so React tooling recognises them as hooks. That definition is worth unpacking slowly, because custom hook naming shows up in almost every React screen you will build. When you can say in plain words what custom hook naming does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The use prefix is not decoration: it tells linters and readers that the function calls hooks and must follow their rules.
Working with custom hook naming usually follows a small, repeatable pattern. First, start the name with use. Next, call hooks only at the top level inside it. Finally, return a clear, documented set of values. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function useDocumentTitle(title) {
useEffect(() => { document.title = title; }, [title]);
}
Read the example from the top down. UseDocumentTitle is clearly a hook by its name, and it follows the same top-level rule as any built-in hook. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with custom hook naming is calling a custom hook conditionally, which breaks the order rule just as with a built-in hook. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why must a custom hook name start with use?
Section 28 — Syncing with an external system in effects
Syncing with an external system means keeping React state and something outside React, such as a chat connection, in agreement through an effect. That definition is worth unpacking slowly, because syncing in effects shows up in almost every React screen you will build. When you can say in plain words what syncing in effects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Effects are the right place for syncing, because they run after rendering and can clean up when the synced value changes or the component leaves.
Working with syncing in effects usually follows a small, repeatable pattern. First, connect to the outside system inside the effect. Next, use the current synced value inside it. Finally, return a cleanup that disconnects. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const connection = connect(room);
return () => connection.disconnect();
}, [room]);
Read the example from the top down. When room changes, React cleans up the old connection and opens a new one, so the outside system always matches the current room. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with syncing in effects is opening a connection in an effect without cleanup, so old connections keep running alongside new ones. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What does it mean for an effect to sync with an external system?
Section 29 — Effects and StrictMode double invoking
In development, React StrictMode may run effects twice, once to set up and once to clean up and set up again, to expose effects that are not properly cleaned up. That definition is worth unpacking slowly, because StrictMode and effects shows up in almost every React screen you will build. When you can say in plain words what StrictMode and effects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. This double run is a development check, not a production behaviour to fear, and it rewards effects whose setup and cleanup are true inverses.
Working with StrictMode and effects usually follows a small, repeatable pattern. First, write setup that can run twice safely. Next, write cleanup that fully undoes setup. Finally, avoid relying on an effect running exactly once for a side action. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
Read the example from the top down. With proper cleanup, the double run leaves exactly one timer, because the first timer is cleared before the second is created. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with StrictMode and effects is treating the development double run as a bug in your code, instead of checking whether cleanup is missing. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why might an effect run twice in development?
Section 30 — Debouncing input with an effect
Debouncing means waiting until the user pauses typing before acting, such as searching, and an effect with a timer and cleanup implements it. That definition is worth unpacking slowly, because debouncing with an effect shows up in almost every React screen you will build. When you can say in plain words what debouncing with an effect does, you can decide where it belongs, what data it needs, and what should happen when that data changes. It avoids a request on every keystroke, which is wasteful and can return results out of order for fast typists.
Working with debouncing with an effect usually follows a small, repeatable pattern. First, start a timer inside an effect that depends on the input. Next, perform the search when the timer fires. Finally, clear the timer in cleanup when the input changes. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const id = setTimeout(() => search(query), 300);
return () => clearTimeout(id);
}, [query]);
Read the example from the top down. Each keystroke clears the previous timer, so search runs only after a pause, with the latest query. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with debouncing with an effect is forgetting the cleanup, so every keystroke still triggers its own delayed search. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How does cleanup make debouncing work?
Section 31 — Timers and intervals with effects
Timers and intervals belong in effects because they are outside work that must start after rendering and stop when the component leaves or the timing changes. That definition is worth unpacking slowly, because timers in effects shows up in almost every React screen you will build. When you can say in plain words what timers in effects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. An effect with cleanup is the safe container for a timer: it starts the timer, and it guarantees the timer is cleared at the right moment.
Working with timers in effects usually follows a small, repeatable pattern. First, create the timer inside the effect. Next, store its id in a ref or a local variable. Finally, clear it in the returned cleanup function. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, []);
Read the example from the top down. The interval ticks once per second, and cleanup clears it, so the counter stops cleanly when the component unmounts. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with timers in effects is creating an interval during rendering, which would create a new interval on every render. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Where should you create an interval, and why?
Section 32 — Listening to window events
Listening to a window event, such as resize, means adding a listener in an effect and removing it in cleanup. That definition is worth unpacking slowly, because window event listeners shows up in almost every React screen you will build. When you can say in plain words what window event listeners does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The effect pattern guarantees the listener exists while the component is present and is removed when it leaves, preventing stale handlers.
Working with window event listeners usually follows a small, repeatable pattern. First, add the listener inside the effect. Next, update state from the handler when the event fires. Finally, remove the same listener in cleanup. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
Read the example from the top down. Resize updates width state, and cleanup removes exactly the handler that was added, so no duplicate handlers accumulate. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with window event listeners is removing a different function reference in cleanup, so the original listener is never actually removed. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why must cleanup remove the same handler that was added?
Section 33 — useLayoutEffect concept
UseLayoutEffect is like useEffect, but it runs before the browser paints, so it is for reading layout and making a visual change without a flicker. That definition is worth unpacking slowly, because useLayoutEffect shows up in almost every React screen you will build. When you can say in plain words what useLayoutEffect does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Most effects should use useEffect, and useLayoutEffect is reserved for cases where a paint with the old layout would be visible.
Working with useLayoutEffect usually follows a small, repeatable pattern. First, measure or adjust layout inside the effect. Next, keep the work small and synchronous. Finally, prefer useEffect unless a flicker is actually visible. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useLayoutEffect(() => {
const height = ref.current.offsetHeight;
setMeasured(height);
}, []);
Read the example from the top down. Measuring before paint lets the component adjust in the same frame, instead of painting once and then jumping. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useLayoutEffect is using useLayoutEffect for ordinary data fetching, which can block painting and slow the screen. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: When should you prefer useLayoutEffect over useEffect?
Section 34 — useId for accessible identifiers
UseId generates a stable, unique id for a component instance, useful for linking a label to an input or an error message to a field. That definition is worth unpacking slowly, because useId shows up in almost every React screen you will build. When you can say in plain words what useId does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Stable ids prevent collisions when the same component appears several times on a page, which hand-written ids easily cause.
Working with useId usually follows a small, repeatable pattern. First, call useId at the top of the component. Next, use the returned id for the label and input pair. Finally, do not use the id for list keys or generated content. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function Field({ label }) {
const id = useId();
return (<><label htmlFor={id}>{label}</label><input id={id} /></>);
}
Read the example from the top down. Each Field instance gets its own id, so its label always points at its own input, even with several fields on the page. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with useId is using useId output as a list key, which it is not designed for. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What problem does useId solve?
Section 35 — Transitions and deferred values
A transition marks a state update as non-urgent, so React can keep the interface responsive while it prepares the new screen, and a deferred value is a lagging copy for the same purpose. That definition is worth unpacking slowly, because transitions and deferred values shows up in almost every React screen you will build. When you can say in plain words what transitions and deferred values does, you can decide where it belongs, what data it needs, and what should happen when that data changes. They help when typing or clicking should feel instant, even though the resulting list or screen is expensive to render.
Working with transitions and deferred values usually follows a small, repeatable pattern. First, keep the urgent update, such as the typed text, immediate. Next, mark the expensive update as a transition. Finally, let React show the old list briefly while the new one prepares. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [isPending, startTransition] = useTransition();
startTransition(() => setQuery(q));
Read the example from the top down. The query update is marked non-urgent, so the input stays responsive while the filtered list catches up. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with transitions and deferred values is marking every update as a transition, which can delay feedback the user actually needs immediately. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What is a transition update for?
Section 36 — Hook order and conditional hooks bug
The conditional hooks bug happens when a hook sometimes runs and sometimes does not, shifting the order of later hooks and mixing up their stored values. That definition is worth unpacking slowly, because conditional hooks bug shows up in almost every React screen you will build. When you can say in plain words what conditional hooks bug does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Because React matches hooks by order, not by name, a skipped hook makes every later hook read the wrong stored value.
Working with conditional hooks bug usually follows a small, repeatable pattern. First, never call a hook inside a condition. Next, never call a hook inside a loop. Finally, move the condition inside the component logic after all hooks. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [a, setA] = useState(0);
if (a > 0) { /* do not put a hook here */ }
Read the example from the top down. Hooks stay above the condition, so their order never changes, even when the condition result changes. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with conditional hooks bug is adding a hook inside an early return, which changes the hook count for some renders. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why does a hook inside a condition break React?
Section 37 — Stale closures
A stale closure is a function that captured an old value from an earlier render and keeps using it after that value has changed. That definition is worth unpacking slowly, because stale closures shows up in almost every React screen you will build. When you can say in plain words what stale closures does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Closures capture values at the moment they are created, so a timer or handler created in an old render may not see the latest state.
Working with stale closures usually follows a small, repeatable pattern. First, identify which values a closure uses. Next, include those values in dependencies or use a functional update. Finally, prefer a ref when you truly need the latest value without re-creating. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
setCount(prev => prev + 1); // uses latest, not captured
Read the example from the top down. The functional update receives the latest count, so it cannot be stale in the way a captured variable can. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with stale closures is capturing a state value in a timer callback and wondering why it never changes. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What is a stale closure?
Section 38 — State updates inside effects and loops
An effect that updates state it also depends on can loop, because each update triggers a render, which triggers the effect again. That definition is worth unpacking slowly, because updates inside effects shows up in almost every React screen you will build. When you can say in plain words what updates inside effects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Loops are avoided by depending on the right values, guarding the update, or moving the update into an event handler instead.
Working with updates inside effects usually follows a small, repeatable pattern. First, check whether the effect both reads and writes the same value. Next, add a guard so the update only runs when needed. Finally, consider whether the work belongs in a handler. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
if (count < 10) setCount(count + 1);
}, [count]);
Read the example from the top down. The guard stops the loop at ten, but a better design often avoids the self-updating effect entirely. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with updates inside effects is writing an effect that sets state unconditionally on every run, creating an endless render loop. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How can an effect create a render loop?
Section 39 — Derived state versus effects
Derived state means calculating a value during rendering from existing state, instead of syncing it with an effect into separate state. That definition is worth unpacking slowly, because derived state versus effects shows up in almost every React screen you will build. When you can say in plain words what derived state versus effects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Syncing with an effect creates two copies that can disagree, while deriving during rendering guarantees they always agree.
Working with derived state versus effects usually follows a small, repeatable pattern. First, ask whether the value can be calculated from current state. Next, if yes, calculate it during rendering. Finally, reserve effects for syncing with outside systems. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const fullName = `${first} ${last}`;
Read the example from the top down. FullName is derived on every render from first and last, so it can never be out of date. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with derived state versus effects is using an effect to copy a prop into state, then forgetting to update it when the prop changes. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: When should you derive a value instead of using an effect?
Section 40 — Splitting effects by purpose
Splitting effects means writing one effect per independent purpose, rather than one giant effect that does several unrelated things. That definition is worth unpacking slowly, because splitting effects shows up in almost every React screen you will build. When you can say in plain words what splitting effects does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Separate effects have separate dependency lists and separate cleanups, so each one runs exactly when its own purpose requires.
Working with splitting effects usually follows a small, repeatable pattern. First, list the independent jobs an effect does. Next, give each job its own useEffect call. Finally, give each its own dependency list and cleanup. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => { document.title = title; }, [title]);
useEffect(() => { connect(chat); }, [chat]);
Read the example from the top down. Title and chat syncing are independent, so they belong in separate effects with separate dependencies. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with splitting effects is combining unrelated setup into one effect, so a change to one purpose re-runs all of them. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why split effects by purpose?
Section 41 — Sharing logic versus sharing state
A custom hook shares logic, not state: each component that calls it gets its own independent state, even though the code is shared. That definition is worth unpacking slowly, because sharing logic versus state shows up in almost every React screen you will build. When you can say in plain words what sharing logic versus state does, you can decide where it belongs, what data it needs, and what should happen when that data changes. This distinction matters when you expect two components to stay in sync: shared logic alone will not sync them, and shared state needs a different mechanism.
Working with sharing logic versus state usually follows a small, repeatable pattern. First, use a custom hook to share behaviour. Next, use lifted state or context to share a value. Finally, do not confuse the two when debugging sync problems. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function useToggle() {
const [on, setOn] = useState(false);
return [on, () => setOn(!on)];
}
Read the example from the top down. Two components calling useToggle each toggle independently, because the hook code is shared but the state is not. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with sharing logic versus state is expecting two components using the same hook to toggle together. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Does a custom hook share state between components?
Section 42 — Testing hooks mindset
Testing hooks means testing the behaviour they produce, such as what a component shows after an interaction, rather than testing hook internals. That definition is worth unpacking slowly, because testing hooks shows up in almost every React screen you will build. When you can say in plain words what testing hooks does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Behaviour tests survive refactors: if you rewrite a hook but the component still behaves the same, the tests still pass.
Working with testing hooks usually follows a small, repeatable pattern. First, render the component that uses the hook. Next, simulate the user interaction. Finally, assert on what the screen shows afterwards. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
Read the example from the top down. A test can click the button and expect the number to increase, without caring which hook implements the count. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with testing hooks is testing that useState was called, rather than testing what the user sees and can do. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What should a hook test assert on?
Section 43 — Performance re-renders and hooks
Performance with hooks means understanding that state changes re-render the component and its children, and that memoisation only helps where that cost matters. That definition is worth unpacking slowly, because performance and hooks shows up in almost every React screen you will build. When you can say in plain words what performance and hooks does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Most re-renders are cheap, so the goal is to fix real slowness, not to prevent every re-render on principle.
Working with performance and hooks usually follows a small, repeatable pattern. First, profile before optimising. Next, memoise only the expensive subtree or calculation. Finally, keep state as low in the tree as it can live. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const [query, setQuery] = useState("");
Read the example from the top down. Query state lives in the search component, so typing does not force unrelated parts of the app to re-render. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with performance and hooks is memoising everything after hearing that re-renders are bad, without measuring any actual slowness. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: When is a re-render a performance problem?
Section 44 — Forms with hooks patterns
Forms with hooks usually use one state value per field or one object for the whole form, with controlled inputs and a submit handler. That definition is worth unpacking slowly, because forms with hooks shows up in almost every React screen you will build. When you can say in plain words what forms with hooks does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The pattern keeps the form data in React state, so validation and submission can read the full, current form in one place.
Working with forms with hooks usually follows a small, repeatable pattern. First, store each field value in state. Next, update it in onChange. Finally, read all values in the submit handler. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function Form() {
const [email, setEmail] = useState("");
return <form onSubmit={e => { e.preventDefault(); save(email); }}><input value={email} onChange={e => setEmail(e.target.value)} /></form>;
}
Read the example from the top down. Submit reads email from state, and preventDefault stops the browser reloading the page on submit. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with forms with hooks is forgetting preventDefault, so submitting the form reloads the page and loses the state. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What does a form submit handler usually do?
Section 45 — Controlled versus uncontrolled with hooks
A controlled input gets its value from state, while an uncontrolled input keeps its own value and is read through a ref when needed. That definition is worth unpacking slowly, because controlled versus uncontrolled shows up in almost every React screen you will build. When you can say in plain words what controlled versus uncontrolled does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Controlled inputs suit validation and formatting as you type, while uncontrolled inputs suit simple, one-off reads such as a file picker.
Working with controlled versus uncontrolled usually follows a small, repeatable pattern. First, use controlled for validated or formatted fields. Next, use uncontrolled with a ref for a simple read-on-submit field. Finally, do not switch a field between the two during its life. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
const ref = useRef(null);
<input ref={ref} defaultValue="Hi" />;
Read the example from the top down. This uncontrolled input starts with Hi and keeps its own value, which the code can read from the ref on submit. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with controlled versus uncontrolled is starting an input uncontrolled and later giving it a value prop, which makes React warn about switching. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: When would you choose an uncontrolled input?
Section 46 — Managing related values with a reducer
A reducer manages related values together, so an action can update several of them consistently in one place. That definition is worth unpacking slowly, because reducer for related values shows up in almost every React screen you will build. When you can say in plain words what reducer for related values does, you can decide where it belongs, what data it needs, and what should happen when that data changes. When values must change together, a reducer prevents half-updated states where one value changed but its partner did not.
Working with reducer for related values usually follows a small, repeatable pattern. First, group the related values into one state object. Next, write actions for the combined changes. Finally, return a complete new state from each action. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function reducer(state, action) {
if (action.type === "next") return { ...state, step: state.step + 1 };
return state;
}
Read the example from the top down. Next advances the step while keeping the rest of the form state intact, in one consistent update. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with reducer for related values is updating related values with separate setters in different places, so they can drift apart. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Why use a reducer for values that change together?
Section 47 — Persisting state with an effect
Persisting state means saving it to browser storage in an effect, so it survives a reload, and reading it back as the initial state. That definition is worth unpacking slowly, because persisting state shows up in almost every React screen you will build. When you can say in plain words what persisting state does, you can decide where it belongs, what data it needs, and what should happen when that data changes. The effect runs when the state changes, writing the latest value, while the initial state reads the stored value once.
Working with persisting state usually follows a small, repeatable pattern. First, read the stored value for the initial state. Next, write the new value in an effect that depends on it. Finally, handle a missing or invalid stored value gracefully. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
localStorage.setItem("name", name);
}, [name]);
Read the example from the top down. Every name change writes the new value to storage, so a reload can restore it instead of starting blank. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with persisting state is writing to storage during rendering, which runs more often than intended and can cause errors. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: How do you persist a state value across reloads?
Section 48 — Cleanup for subscriptions
A subscription in an effect must be unsubscribed in cleanup, so the component stops receiving updates after it leaves. That definition is worth unpacking slowly, because subscription cleanup shows up in almost every React screen you will build. When you can say in plain words what subscription cleanup does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Without cleanup, a removed component can still receive updates and try to set state, causing warnings and wasted work.
Working with subscription cleanup usually follows a small, repeatable pattern. First, subscribe inside the effect. Next, return a function that unsubscribes. Finally, use the same subscription reference in cleanup. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
const sub = store.subscribe(setData);
return () => sub.unsubscribe();
}, []);
Read the example from the top down. Subscribe and unsubscribe are inverses, so the component receives updates only while it is present. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with subscription cleanup is subscribing in an effect and never unsubscribing, so updates keep arriving after unmount. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: What must an effect return when it subscribes?
Section 49 — Common hooks mistakes
Common hooks mistakes include conditional hooks, missing dependencies, stale closures, and effects that loop by updating their own dependencies. That definition is worth unpacking slowly, because common hooks mistakes shows up in almost every React screen you will build. When you can say in plain words what common hooks mistakes does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Each mistake has a recognisable symptom, so learning the list helps you diagnose a bug from its behaviour rather than guessing.
Working with common hooks mistakes usually follows a small, repeatable pattern. First, check hook order when values seem mixed up. Next, check dependencies when an effect seems stale. Finally, check guards when renders never stop. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
useEffect(() => {
console.log(count);
}, [count]);
Read the example from the top down. This correct dependency list is the contrast case: the effect logs the current count, not a stale one. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with common hooks mistakes is adding a dependency to silence a warning without understanding why the effect needed it. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: Name three common hooks mistakes.
Section 50 — Putting hooks together
A small complete example ties hooks together: state holds a query, an effect debounces a search, a ref focuses the input, and a custom hook packages the fetch. That definition is worth unpacking slowly, because hooks together shows up in almost every React screen you will build. When you can say in plain words what hooks together does, you can decide where it belongs, what data it needs, and what should happen when that data changes. Seeing hooks combined shows their division of labour: state remembers, effects sync, refs reach the DOM, and custom hooks share the pattern.
Working with hooks together usually follows a small, repeatable pattern. First, let state hold the query. Next, let an effect debounce and fetch. Finally, let a ref handle focus and a hook package the fetch. None of these steps is complicated on its own, and that is the point: React rewards small, predictable steps repeated consistently. If a step feels mysterious, slow down and name the data involved, the component that owns it, and the moment it changes. That habit turns a confusing screen into a short list of decisions you can check one at a time, instead of a large puzzle you must solve all at once.
Here is a short example you can read line by line:
function Search() {
const [query, setQuery] = useState("");
const ref = useRef(null);
return <input ref={ref} value={query} onChange={e => setQuery(e.target.value)} />;
}
Read the example from the top down. Search combines state, a ref, and a controlled input in a few lines, and an effect and custom hook could complete the fetch. Notice what is fixed and what varies: the fixed parts give the component its shape, while the varying parts come from props, state, or a value calculated during rendering. Try changing one varying value in your head and predicting the new output before you run anything. If your prediction is wrong, that mismatch is useful information, because it shows exactly which part of your mental model needs fixing. Developers who predict before they run learn faster than developers who change code at random and hope the result improves.
A common mistake with hooks together is putting all of this logic in one component without extracting a hook when it is reused elsewhere. The fix is usually boring in a good way: go back to the definition, check the data flowing in, and confirm that each value has one clear owner. Ask yourself three questions before you change code: what should this show, where does that value come from, and what event should change it? If you cannot answer all three, you have found the gap.
To check your own work, build the smallest version that could possibly work, run it, and compare what you see with what you expected. If it matches, add one more feature and check again. If it does not match, look at the data first: log the value, read it carefully, and ask whether it is the value you intended.
Check: In the final example, which hook holds the query, and which reaches the input element?
Practical task
Build a small notes app with hooks: a controlled input for a new note, a list stored in state, a custom hook that persists notes to browser storage with an effect, a debounced search input that filters the list, and a focus button that uses a ref to focus the new-note input. Add loading-style empty states for no notes and no search results, and check that cleanup, dependencies, and hook order all behave correctly.
Chapter questions
- Explain the Rules of Hooks and why hook order matters.
- When should work go in an effect, and when should it go in an event handler?
- Write a custom hook that toggles a boolean and returns the value and a toggle function.
- A timer keeps running after its component unmounts. What is missing, and where does it go?
- Compare useState and useReducer: when would you choose each?