Runnable lesson · press Try it on the examples to edit and run them here.
Each challenge has broken code that you can run. Run it, see the problem, find the cause, fix it in the editor, then open the solution to compare. Use the Debugging routine and the error reference.
Challenge 1: the cart count does not change
Press Add several times. The number never changes. Why?
function App() {
const [items, setItems] = React.useState([]);
function add() {
items.push("item");
setItems(items);
}
return (
<div>
<p>Items: {items.length}</p>
<button onClick={add}>Add</button>
</div>
);
}
Exercise: Solution 1
Cause: the array is mutated and the same reference is given back, so React sees no change (Avoiding State Mutation). —solution
function App() {
const [items, setItems] = React.useState([]);
function add() {
setItems([...items, "item"]);
}
return (
<div>
<p>Items: {items.length}</p>
<button onClick={add}>Add</button>
</div>
);
}
Challenge 2: adds one instead of three
The button should add three each time. It adds one.
function App() {
const [score, setScore] = React.useState(0);
function addThree() {
setScore(score + 1);
setScore(score + 1);
setScore(score + 1);
}
return (
<div>
<p>Score: {score}</p>
<button onClick={addThree}>Add three</button>
</div>
);
}
Exercise: Solution 2
Cause: all three calls use the same snapshot value of score (State as a Snapshot). Use the updater form. —solution
function App() {
const [score, setScore] = React.useState(0);
function addThree() {
setScore((s) => s + 1);
setScore((s) => s + 1);
setScore((s) => s + 1);
}
return (
<div>
<p>Score: {score}</p>
<button onClick={addThree}>Add three</button>
</div>
);
}
Challenge 3: the timer stops at 1
The timer should keep counting. It stops at 1.
function App() {
const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => setSeconds(seconds + 1), 1000);
return () => clearInterval(id);
}, []);
return <p>Seconds: {seconds}</p>;
}
Exercise: Solution 3
Cause: the effect captured seconds = 0 and never sees newer values, a stale closure from a missing dependency (Effect Dependencies). The best fix is an updater function so the effect needs no dependencies. —solution
function App() {
const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id);
}, []);
return <p>Seconds: {seconds}</p>;
}
Challenge 4: the text moves to the wrong row
Type “keep me” in the first box, then press Remove first. The text stays in the first box, but now beside “Banana”. Why?
function App() {
const [fruits, setFruits] = React.useState(["Apple", "Banana", "Cherry"]);
return (
<div>
<button onClick={() => setFruits(fruits.slice(1))}>Remove first</button>
<ul>
{fruits.map((fruit, index) => (
<li key={index}>
{fruit} <input aria-label={"Row " + (index + 1)} />
</li>
))}
</ul>
</div>
);
}
Exercise: Solution 4
Cause: the array index is used as the key, so React matches rows by position and reuses the first row’s input for “Banana” (Understanding React Keys). Use a stable id: here the fruit name is unique and stable, so it can be the key. —solution
function App() {
const [fruits, setFruits] = React.useState(["Apple", "Banana", "Cherry"]);
return (
<div>
<button onClick={() => setFruits(fruits.slice(1))}>Remove first</button>
<ul>
{fruits.map((fruit, index) => (
<li key={fruit}>
{fruit} <input aria-label={"Row " + (index + 1)} />
</li>
))}
</ul>
</div>
);
}
Now the input that held “keep me” belonged to Apple, so it disappears with Apple.
Challenge 5: “Maximum update depth exceeded”
This component never settles: it keeps re-rendering, and the console fills with “Maximum update depth exceeded” warnings. Find the loop.
function App() {
const [list, setList] = React.useState([]);
const options = { sort: "asc" };
React.useEffect(() => {
setList([1, 2, 3].sort());
}, [options]);
return <p>Items: {list.length}</p>;
}
Exercise: Solution 5
Cause: options is a new object on every render, so the effect depends on something that changes every time. The effect sets state, which renders again, which creates a new options, and so on forever (Effect Dependencies). Move the constant outside the component, or depend on the primitive value. —solution
function App() {
const [list, setList] = React.useState([]);
React.useEffect(() => {
setList([1, 2, 3].sort());
}, []); // the effect uses nothing from the component, so no dependencies
return <p>Items: {list.length}</p>;
}
Challenge 6: nothing renders
The list is empty although the array has three names.
function App() {
const names = ["Ada", "Grace", "Linus"];
return (
<div>
<h3>Names</h3>
<ul>
{names.map((name) => {
<li key={name}>{name}</li>;
})}
</ul>
</div>
);
}
Exercise: Solution 6
Cause: an arrow function with curly braces needs an explicit return. Without it map returns undefined for each item, so nothing is rendered (Using map()). Use parentheses for an implicit return, or write return. —solution
function App() {
const names = ["Ada", "Grace", "Linus"];
return (
<div>
<h3>Names</h3>
<ul>
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
</div>
);
}
What to do when you are stuck
Reproduce, read the first error, check the data, form a guess and test it. When it is hard, reduce it to a small example in the playground. Often the act of making the example shows the bug.