Chapter 33 — State Management Basics
Section 1 — What state is
State is the information a page remembers while it is being used, such as what is typed, what is selected, what is open, and what has been loaded. It is distinct from the fixed markup, because it changes in response to the user. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet what state is, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If what state is is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of what state is, and when would you use it?
Section 2 — State versus the display
The display is what the user sees, while state is the value behind it. When the two disagree, the page looks broken, so a reliable rule is to let the display follow from state instead of updating the screen by hand in many places. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet state versus the display, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If state versus the display is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of state versus the display, and when would you use it?
Section 3 — Local component state
Local state belongs to one component, such as whether a single menu is open or what one input contains. Keeping it local keeps the rest of the page unaffected when it changes, which makes the component easier to reuse and test. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet local component state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If local component state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of local component state, and when would you use it?
Section 4 — When state should be shared
State should be shared when two or more parts of the page need the same value, such as a selected item shown in both a list and a detail view. Sharing it in one place prevents the two views from drifting apart. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet when state should be shared, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If when state should be shared is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of when state should be shared, and when would you use it?
Section 5 — Lifting state up
Lifting state up means moving a value to the nearest common parent of the components that need it, then passing it down. The parent becomes the owner, and the children display it or ask for changes through callbacks. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet lifting state up, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If lifting state up is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of lifting state up, and when would you use it?
Section 6 — Props drilling
Props drilling is passing a value through several layers of components that do not themselves use it, just to reach a deeper child. A little drilling is normal, but deep drilling makes changes painful and hides where a value really comes from. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet props drilling, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If props drilling is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of props drilling, and when would you use it?
Section 7 — Derived state
Derived state is a value that can be calculated from other state, such as a total from a list of prices or a filtered list from a search term. It should usually be computed when needed, not stored separately, or the two copies can disagree. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet derived state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If derived state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of derived state, and when would you use it?
Section 8 — Storing versus computing
A value should be stored only if it cannot be worked out from something else, or if remembering it is itself the point, such as text the user typed. Everything else is a candidate for computing, which keeps one source of truth. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet storing versus computing, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If storing versus computing is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of storing versus computing, and when would you use it?
Section 9 — A single source of truth
A single source of truth means each piece of information lives in exactly one place. Other parts of the page read it from there, so an update in one place is visible everywhere, and there is no second copy to forget to update. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet a single source of truth, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If a single source of truth is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of a single source of truth, and when would you use it?
Section 10 — Immutability in plain words
Immutability means creating a new value instead of changing the old one in place, for example building a new list with one item added rather than editing the existing list. This makes changes easy to detect and to undo. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet immutability in plain words, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If immutability in plain words is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of immutability in plain words, and when would you use it?
Section 11 — Why changing values in place hurts
Changing a value in place can surprise code that already holds a reference to it, because it sees the new value without any update having been announced. That hidden change is a common source of screens that do not refresh when they should. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet why changing values in place hurts, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If why changing values in place hurts is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of why changing values in place hurts, and when would you use it?
Section 12 — Updating lists safely
To update a list safely, build a new list: filter out a removed item, map over items to change one, or spread the old items plus a new one. The original list stays untouched, so the change is explicit and easy to follow. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet updating lists safely, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If updating lists safely is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of updating lists safely, and when would you use it?
Section 13 — Updating objects safely
To update an object safely, copy it and change one field in the copy, leaving the original unchanged. Nested objects need the same care at each level that changes, because copying only the outer object still shares the inner ones. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet updating objects safely, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If updating objects safely is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of updating objects safely, and when would you use it?
Section 14 — The reducer idea
A reducer is a function that takes the current state and a description of what happened, called an action, and returns the next state. It keeps all the update logic in one place, which makes complex changes easier to reason about. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet the reducer idea, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If the reducer idea is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of the reducer idea, and when would you use it?
Section 15 — Actions describe what happened
An action is a plain description, such as that an item was added or a filter was cleared, not the code that performs the change. Separating the description from the update makes changes easy to log, test, and explain. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet actions describe what happened, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If actions describe what happened is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of actions describe what happened, and when would you use it?
Section 16 — Choosing useState or useReducer
A single independent value, like a toggle, suits a simple state hook, while several related values that change together suit a reducer. The deciding question is whether updates need to consider several fields at once. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet choosing usestate or usereducer, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If choosing usestate or usereducer is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of choosing usestate or usereducer, and when would you use it?
Section 17 — Context for shared values
Context lets a value be read by many components without passing it through every layer in between. It suits values that many parts need, such as the current theme or the signed-in user, but it is not a replacement for careful state design. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet context for shared values, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If context for shared values is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of context for shared values, and when would you use it?
Section 18 — Context is not a store
Context is mainly a way to pass a value down a tree, not a complete state management system on its own. It does not by itself provide update rules, history, or performance controls, so large apps often pair it with a reducer or a store. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet context is not a store, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If context is not a store is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of context is not a store, and when would you use it?
Section 19 — What a store is
A store is a central place that holds state outside any single component, with defined ways to read it and to update it. Components subscribe to the parts they need, so the same data can be used across pages without being passed down manually. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet what a store is, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If what a store is is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of what a store is, and when would you use it?
Section 20 — Selectors read slices of state
A selector is a small function that reads one slice of state, such as only the items in a cart or only the current filter. Good selectors let components depend on exactly what they display, and nothing more. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet selectors read slices of state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If selectors read slices of state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of selectors read slices of state, and when would you use it?
Section 21 — Why components should read little
A component that reads only what it displays will re-render only when that slice changes. A component that reads the whole store will re-render for unrelated changes, which wastes work and makes performance harder to predict. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet why components should read little, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If why components should read little is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of why components should read little, and when would you use it?
Section 22 — State and re-rendering
When state changes, the components that depend on it re-render to show the new value. Understanding which components re-render, and why, is essential, because unnecessary re-renders are a common performance cost in state-heavy pages. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet state and re-rendering, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If state and re-rendering is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of state and re-rendering, and when would you use it?
Section 23 — Batching updates
Batching means grouping several state updates that happen together into a single re-render, rather than redrawing after each one. This keeps rapid changes efficient and prevents the user from seeing intermediate states that were never intended. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet batching updates, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If batching updates is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of batching updates, and when would you use it?
Section 24 — Asynchronous state
Asynchronous state describes data that arrives later, such as a list fetched from a server. It is not just the data: it also includes whether the request is loading, whether it failed, and whether the data is stale. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet asynchronous state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If asynchronous state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of asynchronous state, and when would you use it?
Section 25 — Loading states
A loading state tells the user that work is happening, such as a spinner or a skeleton layout, instead of leaving a blank screen. Without it, users may click repeatedly or assume the page is broken. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet loading states, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If loading states is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of loading states, and when would you use it?
Section 26 — Error states
An error state records that a request failed and gives the user a way forward, such as a clear message and a retry button. Swallowing errors, or showing only a blank area, leaves users with no way to recover. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet error states, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If error states is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of error states, and when would you use it?
Section 27 — Empty states
An empty state is what a page shows when there is legitimately nothing to display, such as an empty cart or no search results. It should explain the situation and suggest a next step, rather than looking like a loading failure. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet empty states, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If empty states is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of empty states, and when would you use it?
Section 28 — Form state
Form state is the current contents of a form's fields, plus validation results and whether the form has been touched or submitted. Keeping it in one place makes validation, resetting, and submission far easier to manage. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet form state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If form state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of form state, and when would you use it?
Section 29 — Controlled inputs
A controlled input is one whose value is held in state and updated on every keystroke, so the code always knows what the user typed. This gives full control over validation and formatting, at the cost of more updates. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet controlled inputs, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If controlled inputs is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of controlled inputs, and when would you use it?
Section 30 — Uncontrolled inputs
An uncontrolled input keeps its own value in the browser, and code reads it only when needed, such as on submit. This is simpler for basic forms, but harder when validation or live formatting must happen as the user types. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet uncontrolled inputs, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If uncontrolled inputs is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of uncontrolled inputs, and when would you use it?
Section 31 — Validation as state
Validation results are state too: whether each field is valid, and what message to show if not. Treating validation as derived from the current values, rather than as a separate copy, prevents messages that no longer match the input. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet validation as state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If validation as state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of validation as state, and when would you use it?
Section 32 — URL as state
The URL can hold state, such as the current search term, page number, or selected tab, in a form users can bookmark and share. State in the URL survives a refresh and a shared link, which local component state does not. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet url as state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If url as state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of url as state, and when would you use it?
Section 33 — Choosing URL, local, or global state
Put shareable, navigable choices in the URL, page-local choices in local state, and widely needed data in shared state. Asking where the value must survive — a refresh, a share, or only this screen — usually settles the choice. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet choosing url, local, or global state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If choosing url, local, or global state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of choosing url, local, or global state, and when would you use it?
Section 34 — Persisting state
Persisting state means saving it somewhere durable, such as browser storage, so it survives a refresh or a return visit. Only persist values that are safe and useful to restore, such as a draft or a preference, not secrets or stale server data. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet persisting state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If persisting state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of persisting state, and when would you use it?
Section 35 — Saving to local storage
Browser storage lets a page save small values on the user's device and read them back later. It is useful for preferences and drafts, but it is not a database: it is limited, synchronous, and visible to anyone with access to the device. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet saving to local storage, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If saving to local storage is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of saving to local storage, and when would you use it?
Section 36 — Rehydrating state
Rehydration is restoring saved state when a page loads again, such as reading a saved theme or draft and using it as the starting value. It must handle missing, old, or corrupted saved data gracefully, not crash on it. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet rehydrating state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If rehydrating state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of rehydrating state, and when would you use it?
Section 37 — Normalising data
Normalising data means storing each item once, by its identifier, and referring to it by that identifier elsewhere, instead of copying the same item into many lists. This prevents updates in one place from leaving stale copies elsewhere. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet normalising data, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If normalising data is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of normalising data, and when would you use it?
Section 38 — Lists by identifier
When a list arrives as nested objects, it is often better to store a list of identifiers plus a lookup of items by identifier. Adding, removing, or updating one item then touches one place, and every list that references it stays consistent. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet lists by identifier, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If lists by identifier is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of lists by identifier, and when would you use it?
Section 39 — Optimistic updates
An optimistic update changes the screen immediately, before the server confirms, so the page feels instant — for example, showing a liked item as liked at once. If the server later rejects it, the change must be rolled back and the user told. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet optimistic updates, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If optimistic updates is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of optimistic updates, and when would you use it?
Section 40 — Pessimistic updates
A pessimistic update waits for the server's confirmation before changing the screen. It is slower to feel, but safer when the action must not appear to succeed unless it really did, such as a payment or a booking. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet pessimistic updates, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If pessimistic updates is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of pessimistic updates, and when would you use it?
Section 41 — Undo and history
Undo is possible when past states are kept, so stepping back means restoring an earlier value rather than trying to reverse an action by guesswork. Keeping a small history of states makes undo, redo, and time-travel debugging straightforward. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet undo and history, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If undo and history is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of undo and history, and when would you use it?
Section 42 — Debugging state
Debugging state starts with inspecting the current value, not the screen: log it, or use developer tools to view it, and compare it with what you expected. Most state bugs are wrong values, not broken rendering, so the value is the evidence. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet debugging state, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If debugging state is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of debugging state, and when would you use it?
Section 43 — State in developer tools
Browser and framework developer tools can show component state, trace which update caused a render, and replay actions. Learning to inspect state directly turns vague reports like 'it shows the wrong thing' into precise diagnoses. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet state in developer tools, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If state in developer tools is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of state in developer tools, and when would you use it?
Section 44 — Testing state logic
State logic is easy to test because a reducer or a selector is a plain function: give it a state and an action, and check the state it returns. Testing updates this way catches mistakes without needing to render a whole page. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet testing state logic, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If testing state logic is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of testing state logic, and when would you use it?
Section 45 — State and accessibility
State changes must be announced or made visible to everyone, including keyboard and screen reader users: a menu that opens should move focus sensibly, and a live result should be announced rather than changing silently. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet state and accessibility, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If state and accessibility is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
When two parts of the screen disagree, trace the value back to its owner instead of patching each display separately, because the disagreement usually lives at the source.
Check: In your own words, what is the key idea of state and accessibility, and when would you use it?
Section 46 — State that leaks between screens
State sometimes leaks when a page is reused for a new item but old values remain, such as a form still showing the previous record. Resetting state when its context changes is a small step that prevents confusing, stale screens. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet state that leaks between screens, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If state that leaks between screens is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [count, setCount] = useState(0);
const add = () => setCount(c => c + 1);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of state that leaks between screens, and when would you use it?
Section 47 — Resetting state safely
Resetting means returning state to its initial value, such as clearing a form after submission or clearing a selection when its data changes. A reset should be deliberate and complete, or a leftover field will mislead the next user of that screen. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet resetting state safely, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If resetting state safely is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [items, setItems] = useState([]);
const addItem = (item) => setItems(list => [...list, item]);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of resetting state safely, and when would you use it?
Section 48 — Derived data with memoisation
Memoisation remembers the result of an expensive calculation and reuses it until its inputs change. It is useful for derived data that is costly to compute, but it should not be used to hide a deeper problem, such as a calculation that runs far too often. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet derived data with memoisation, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If derived data with memoisation is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const [state, dispatch] = useReducer(reducer, initialState);
dispatch({ type: 'added', item });
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of derived data with memoisation, and when would you use it?
Section 49 — When not to use a global store
A global store is not needed for every value: a toggle used by one component, a form field, or a temporary animation value is often better kept local. Reaching for a store by default adds indirection that every reader must follow. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet when not to use a global store, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If when not to use a global store is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const ThemeContext = createContext('light');
const theme = useContext(ThemeContext);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of when not to use a global store, and when would you use it?
Section 50 — Naming state well
Good state names describe what the value means, not how it is displayed — for example, a selected item identifier rather than a highlighted row index. Clear names make updates self-explanatory and reduce the chance of using the wrong value. In plain terms, what the user sees should follow from a value you can point to and name, rather than from a hidden side effect in several places at once. When you first meet naming state well, it can look like a small detail, but it shapes how the rest of the work behaves, so it is worth understanding before the code around it grows.
Why does this matter in real work? Because front-end code is judged by what users experience. If naming state well is handled carelessly, the symptoms are familiar: stale information, a button that works once and then stops, or a form that loses what was typed. Users rarely report the underlying cause; they report that the site feels broken, and they leave. Getting this idea right saves the user's time and your own time when you return to the code later.
How do you apply it in practice? Start by naming the thing you are working with, then state what should happen in a single sentence before you write code. Next, make the smallest change that produces that behaviour, and look at the result in the browser rather than assuming it worked. Then try the awkward cases: an empty value, a very long value, and a rapid repeated action. Those cases are where most front-end ideas fail, not in the happy path where everything is typed correctly and the network is fast.
Here is a short example to make the idea concrete:
const selected = useStore(s => s.selectedId);
const select = useStore(s => s.select);
Read the example slowly and notice what each part contributes. The point is not to memorise the snippet, but to see the pattern: a named value, an action that changes it, and a visible result that follows from it. Try changing one part in your head and predicting the result before you run anything, because a wrong prediction tells you exactly which part of your mental model needs correcting.
A common mistake is to rush past this idea and treat it as obvious, then to improvise a different approach in each part of a project. That is how inconsistency creeps in, and nobody can say which approach is correct. Another common mistake is to add complexity before the simple version has been tried. To check your own work, explain the idea aloud, demonstrate it with a tiny example, and try one case that should fail gracefully.
A good test of your design is to describe the state change aloud before you write it: what event happened, what changed, and what should now look different on screen.
Check: In your own words, what is the key idea of naming state well, and when would you use it?
Practical task
Build a small page that puts this chapter into practice. For Chapter 33, state management basics, create one page with a clear example of the chapter's main ideas, try it in the browser, break one thing deliberately and fix it, and write a short note explaining what you learned and what you would do differently next time. Keep the page small, test it on a narrow screen as well as a wide one, and check that every interaction still behaves as you intended.
Chapter questions
- Explain, in plain words, the central idea of Chapter 33 and why it matters to users, not just to developers.
- Give an example where doing this chapter's topic badly would be visible to a user, and describe what the user would experience.
- Compare two approaches from this chapter and explain when you would choose each one.
- Describe a mistake a beginner commonly makes with this chapter's topic, how you would spot it, and how you would fix it.
- Build a tiny example that demonstrates one idea from this chapter, and explain, step by step, how you would check that it works correctly.