react
react copied to clipboard
Bug: Repeated state setter callbacks when callback throws
It's a minor thing, but confusing: If you call a useState state setter and the callback throws, React calls the state setter callback twice more before allowing the error to propagate to the console (then twice more after that, with a development build). This obscures the nature of the error. I'd expect the callback to be called just once. This happens both with dev and prod builds of the library (though the number of calls varies), and without StrictMode.
React version: 18.2.0
Steps To Reproduce
- Call a state setter passing in a callback that throws
Link to code example: https://codesandbox.io/s/react-state-setter-oddity-40jgq5?file=/src/index.js (but look in the actual console, not the CodeSandbox console, it doesn't show everything).
function App() {
const [value, setValue] = useState(0);
const update = () => {
console.log("calling state setter");
setValue((v) => {
console.log("state setter callback called");
if (Math.random() < 1) {
// ^^^^ Always true, just to fool ESLint
throw new Error("!!"); // <== Stand-in for code that throws unexpectedly
}
console.log("state setter callback returned");
return v + 1;
});
};
return (
<div className="app">
<div>Value: {value}</div>
<input type="button" onClick={update} value="Update" />
</div>
);
}
The current behavior
In development, calling update causes the state setter callback to be called a total of five times: Three prior to the error being reported in the console, then twice more, with the error appearing a total of three times.
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 ...
WIth a production build, the state setter callback is called three times, and the error reported twice (https://thenewtoys.dev/temp/react-repeating-state-setter):
calling state setter
state setter callback called
state setter callback called
state setter callback called
Error: !!
at ...
Uncaught Error: !!
at ...
The expected behavior
Calling update should cause the state setter callback to be called just once, and the error to be reported just once.
calling state setter
state setter called
Uncaught Error: !!
at ...
The above error occurred in...
As I say, it's a minor thing, but the current behavior obscures the nature of the error. In my case, the error I was getting seemed to be a result of the repeated (seemingly-recursive) calls to the callback, so I was looking for a way my code could be doing that (since select isn't broken :-) ). But eventually I realized it was React doing the repeated calls.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Yes, this affects us. It took hours to figure out the first time, and despite best efforts, if we have a but causing a setter to throw again there's every chance we'll fail to remember this and spend hours debugging it again (sad but true :-) ).
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bot - bump
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
bot - bump
This is intended. We render again on error to hope we may recover from the error. That's why state setters must be pure so that we can call them at any point.
This is intended. We render again on error to hope we may recover from the error.
That's an extremely odd and surprising thing to do. I can't think of a single other library I've ever used that would silently repeat a callback that had thrown in hopes it worked the next time. Where is this documented and the rationale explained?
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
sigh Not stale.
This issue has been automatically marked as stale. If this issue is still affecting you, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize it yet. If you have any new additional information, please include it with your comment!
Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please create a new issue with up-to-date information. Thank you!