The useEffect you didn't need
Reviewed a component this week that synced one piece of state into another with an effect:
const [items, setItems] = useState<Item[]>([]);
const [count, setCount] = useState(0);
useEffect(() => {
setCount(items.length);
}, [items]);
This is a bug waiting to happen and an extra render every time. count is not independent state. It is items.length. Deriving it during render deletes the whole problem:
const [items, setItems] = useState<Item[]>([]);
const count = items.length;
The rule I keep coming back to: if you can compute a value from existing state or props during render, it is not state, and it does not belong in an effect. Effects are for synchronizing with something outside React, a subscription, the DOM, a network request. They are not for keeping two useState values in agreement.
Most of the useEffect calls I delete fall into this category. The component gets shorter, the extra render disappears, and a class of “why is this stale” bugs stops being possible.