React Repeating State Update

To save others from the debugging journey I've just been on, a brief note:

If you're using React's useState hook, calling the state setter with a callback, and getting the weird behavior that your callback is being called repeatedly before eventually failing, the problem is that your callback is throwing. Here's an example:

const [value, setValue] = useState(0);
// ...
const handler = () => {
    console.log("calling state setter");
    setValue((v) => {
        console.log("state setter callback called");
        // Stand-in for code that throws unexpectedly. (The condition is
        // always true.)
        if (Math.random() < 1) {
            throw new Error("!!");
        }
        console.log("state setter callback returned");
        return v + 1;
    });
};
// ...code that hooks up `handler` to something...

When you trigger a call to handler, you'll get this in the console:

calling state setter
state setter callback called
state setter callback called
state setter callback called
Uncaught Error: !!
    at ...
state setter callback called
state setter callback called
Uncaught Error: !!
    at ...
The above error occurred in...
Uncaught Error: !!
    at ...

Note that the state setter callback wasn't called once, or twice, but three times before the first time the error was allowed to propagate and show up in the console. And then it was called twice more, followed by the error repeating twice more as well. Very odd! This doesn't only happen with the dev builds of the library, but with production builds as well, although the number of calls to the callback is different. (It's also not related to StrictMode.)

When this happened to me the other day, the error was such that I thought it was a side-effect of the state setter callback getting called repeatedly, so I focussed on why that would be happening (assuming that it was my code doing it, not React, because after all select isn't broken 😉).

But no, the error was occurring the first time, for reasons unrelated to the state setter callback being called repeatedly.

So just a friendly warning: If you're getting this kind of repeated state setter callback calls followed by an error, ignore the repeats, and focus on the error.

Have a question or comment about this post? Ping me on Mastodon at @tjcrowdertech@hachyderm.io!