preactjs / preact

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
https://preactjs.com
MIT License
36.82k stars 1.95k forks source link

Rendering from useEffect can break hooks #2798

Closed ikolobar closed 4 years ago

ikolobar commented 4 years ago

If a component calls render from an effect or effect cleanup, and it rerenders before the effect runs, then its hook state breaks.

Reproduction

https://codesandbox.io/s/stoic-elbakyan-020m2

Steps to reproduce

Click the button. This will increment the counter once immediately, and then again in a microtask (so that it happens before the effect runs from requestAnimationFrame). The effect cleanup renders a dummy blank component.

Expected Behavior

The count should go up by 2 for each click.

Actual Behavior

The count stays 0. With preact/debug it also causes a "Hook can only be invoked from render methods." error.

If you comment out the render() call in the effect, then it works as expected. It also works with useLayoutEffect. What seems to be happening is the second state update flushes the effect from the first state update, and the render call in the effect unexpectedly mutates currentComponent. Similar to #2175.

ikolobar commented 4 years ago

@JoviDeCroock Thanks for the quick fix!

If I'm understanding the change correctly, it fixes the issue of losing the hook value. This setup was also causing a "Hook can only be invoked from render methods." error with preact/debug, because render resets hooksAllowed to false. Is that also fixed by this?

JoviDeCroock commented 4 years ago

It should yes, so basically because currentComponent was null it would result in this behavior. I'll double check 👍