-
Notifications
You must be signed in to change notification settings - Fork 47.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bug: useEffect closure not called when dependencies changed #24042
Comments
commenting out your so this isn't really a bug in React but expected behavior, it's a bug in your code. |
I think you’re misunderstanding the example. Note that the re-render IS triggered, by a state change of the router caused by the Router.replace. The ref also changes, but we do not rely on the ref to cause the re-render. Afaik the useEffect checks its dependency array on every render of the containing component, thus it should call the closure in this case. It does work in most cases, just not in this one. Since it‘s reliably reproducible, it would be really interesting what the underlying issue is. The curious thing is that removing one of the |
I think it has something to do with ref value updating too slow - When in Maybe it's a false trail - I'd say router triggers re-render when the ref value is still not what you'd like it to be. |
In your current behavior
As State of |
But the |
replicated this behavior without let me re-describe the problem: edit: think this is related https://reactjs.org/docs/hooks-reference.html#bailing-out-of-a-state-update |
Thanks @valen214 for a compact repro. There is no bug in React here. The issue is that this code is reading a mutable value during rendering. This makes the behavior unpredictable because the rendering result depends on the moment you read it. When it changes, React won't know to re-render the component. And from React's perspective, if the state has not changed, it's safe to bail out of the update. Concretely, code like this is wrong: function MyComponent() {
let something = window.something // Bad: reading mutable state!
let something2 = someRef.current // Bad: also reading mutable state!
useEffect(() => {
// ...
}, [something, something2]) If you need to read mutable data during rendering, you should either put it in state and update in In the original case (Next.js example), the issue is similarly with Hope this helps! |
Thanks for the explanation. I was aware that ref value changes don‘t cause rerenders, but not that accessing a ref during rendering could be problematic. So to get it right - when react bails out of the update, then It‘s easy for ref values to creep in to the rendering, especially when having nested custom hooks and since there‘s no linting to help prevent this from happening. A bit more forgiving behavior of react would help in that case.
Would
|
It’s similar to reading
Yes.
I’ll ask the team about this. But I wouldn’t count on something so fragile anyway. This should be fixed in the user code.
It’s hard to have a “forgiving” behavior here because one you start breaking the rules, the consequences accumulate and become too difficult to reason about. So I actually think overall it’s better when it doesn’t work at all. I hear you re: it being nice for this to be enforced somehow. We haven’t found a great way to do that yet.
No, it’s fundamentally the same problem: rendering result, including the emitted effects, should only depend on props/state/context. If you read a mutable value during rendering (in memo or not), your component no longer behaves predictably. Calling it with the same inputs would produce different outputs over time. |
Thanks a lot for the detailed explanation! |
So this is also not a good idea, right?
|
@gaearon Can you clarify how you'd solve this problem by storing the mutable value in state via function MyComponent() {
const [something, setSomething] = useState(window.something)
useEffect(() => {
setSomething(window.something);
}, [window.something]); But this won't work; the entire problem is that the effect function is failing to run! So this doesn't guarantee that the mutable Am I misinterpreting your suggested solution? |
@ericbiewener as I‘ve learned above, we are not allowed to use |
@fabb thanks for the quick response, but I'm not sure how |
That‘s only possible if you have registered some listener that listens to changes to that and sets a state in your component so a render is triggered. Depends on your use case and how |
Hi all - I bumped into this issue recently too, I have to admit it's pretty surprising behaviour. I've made a concise demonstration of the bug here if it helps explain the issue any better: https://codesandbox.io/p/sandbox/trusting-tesla In short: const [state, setState] = useState(0);
const dep = useRef(0);
useEffect(fn, [dep.current]);
function bailedUpdate() {
dep.current++;
setState(state);
}
function brokenUpdate() {
setState(state + 1);
}
All of this is covered in the comments above, but I'd like to add that while the above code is pretty contrived (deps pointing to a ref), in practice it's entirely possible that one bit of code doesn't know (or shouldn't know) whether a dep is based on a ref or proper state. Furthermore, it's not always clear that we're calling I'd like to propose that either:
|
I run into a very weird issue where the dependency of
useEffect
changes, but its closure is not called. The component renders correctly with the updated value due to a state change of a parent router, just theuseEffect
closure is not called.React version: 17.0.0.2 and 18.0.0.rc1
Steps To Reproduce
{"status":"updated_value"}
, but nouseEffect
closure call for that value.Link to code example:
https://codesandbox.io/s/useeffect-bug-oz4k9k?file=/pages/index.tsx
https://github.com/fabb/react-useeffect-bug
Please note that it is a reduced test example from a much bigger project, so don‘t wonder about the weird usage of the ref.
The current behavior
In the reproducing example, the
useEffect
closure is not called for the updated value. Further changes correctly call the closure though.The expected behavior
The
useEffect
closure should be called in the reproducing example. We see that the component renders with the updated value, so theuseEffect
dependencies definitely changed.cc @gaearon as discussed on twitter
The text was updated successfully, but these errors were encountered: