esamattis / redux-hooks

⚓ React Hooks implementation for Redux
http://npm.im/@epeli/redux-hooks
MIT License
96 stars 4 forks source link

[question] How do you enforce top-down updates? #9

Open ricokahler opened 5 years ago

ricokahler commented 5 years ago

I was admiring your work and all I have is just a quick question: How did you enforce top-down updates (aka zombie bug)?

I couldn't figure it out by looking at your code.

Anyway, thanks for the library! ❤️

esamattis commented 5 years ago

Thanks!

It uses ReactDOM.unstable_batchedUpdates(). It's an unstable API but the React core team has recommended it's usage in the official react-redux bindings too*. And it is actually how React internally enforces top-down updates in event handlers. You can read the Batching chapter in the Dan's React as a UI Runtime -article.

* react-redux does not itself rely on unstable_batchedUpdates in the new 7.x release for the top-down updates but uses it for perf optimization. Not sure why it's not enough for react-redux. I guess it comes down to avoiding some other rare edge cases. I intend to move this library to use the Provider from react-redux once it ships public hooks api.

The setup process in this library is like this:

Provider

When the HooksProvider mounts it subscribes to the store:

https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L131

and creates a mutable Map instance of downstream update functions and puts it to a React Context:

https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L138

Downstream Consumers

When a component which uses one of the provided Redux hooks mounts it gets the Map() instance from the context

https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L252

and adds its update function to it by mutating the instance:

https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L334

Dispatching

When the store updates the HooksProvider calls the notify function which iterates through all update functions and calls each inside the ReactDOM.unstable_batchedUpdates() which is the important bit in avoiding the zombie bug

https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L114-L118

The update function itself just updates the mapped state ref

https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L321-L329

and forces rendering using setState hook https://github.com/epeli/redux-hooks/blob/ad50256e398169e6578a8a3f7eedd1e8711ff49f/src/redux-hooks.tsx#L228-L234

esamattis commented 5 years ago

Unfortunately I've recently discovered this does not solve the issue completely as shown here

https://codesandbox.io/s/p77jyzjkv7

And at the moment it seems like there's no way to implement fully bug free Redux Hooks without wrapping components...

More info here https://github.com/reduxjs/react-redux/issues/1179#issuecomment-475947847

AllenFang commented 5 years ago

I love this explanation, thanks for sharing these amazing thing and this library!