`<Provider>` misses state changes that occur between when its constructor runs and when it mounts
See original GitHub issueDo you want to request a feature or report a bug?
Bug
What is the current behavior?
If state changes occur during the time after <Provider>
's constructor runs and before <Provider>
is mounted, <Provider>
will miss those changes and won’t make them available to connected components during the render phase of those components.
This is probably a rare use case, but it can occur in a very specific scenario that’s useful in an app that uses both server-side and client rendering and needs to allow dynamically loaded components (such as components loaded when the route changes) to attach new reducers to an existing store.
Here’s a reduced test case that fails in React Redux 6.0: https://codesandbox.io/s/612k3pv1yz
What is the expected behavior?
Connected components should always see the most recent Redux state, even if that state changed between when <Provider>
was constructed and when it was mounted. This was the behavior in 5.x.
Here’s an exact copy of the reduced test case above, but using React Redux 5.1.1 to demonstrate that this works fine there: https://codesandbox.io/s/yvw1vmnkrv
Which versions of Redux, and which browser and OS are affected by this issue? Did this work in previous versions of Redux?
Redux 4.0.1. This worked in React Redux 5.x, but broke in 6.0. It’s unrelated to any specific browser or OS.
More background on this use case
I realize this use case may be a little hard to understand at first glance, so I’ll try to explain in more detail why it’s valuable.
The repro case above simulates a technique that’s useful when using Redux and React Redux in an SSR-compatible app that needs to be able to load components and attach new reducers on demand (such as when the route changes) without knowing up front what components or reducers might eventually be loaded.
This technique is used extensively by Cake. I believe New Twitter uses a similar approach, but they’re still on React Redux 4.x so aren’t yet affected by this bug.
When rendering on the server, we can’t load components during the render phase (because the SSR render phase is synchronous). So instead we need to load all components for a given render pass up front, then attach reducers as needed during the render phase of the app.
Dynamically loaded components may themselves import other components, and components at any level of the import tree could be Redux-connected. This means each component must be able to attach its own reducers. The withRedux
decorator in the repro case simplifies this by wrapping a dynamically loaded component (such as the Route
component in the example) in a higher order component that attaches reducers on demand in its constructor.
Since React constructs <Provider>
before it constructs its children, this means that the Redux store <Provider>
sees at construction time doesn’t yet have a complete set of reducers attached.
Once the child components are constructed, all reducers will have been attached and React will begin to render the component tree, but <Provider>
in React Redux 6.0 passes the old state to all its context consumers during the render phase, breaking any component that expects the new state. <Provider>
doesn’t check for state changes until componentDidMount()
runs, at which point it’s already too late.
Edit: s/Redux/React Redux/ in various places because I’m tired. I do know the difference, I promise!
Edit 2: Clarified that dynamically loaded reducers are attached during the render phase, not before it.
Issue Analytics
- State:
- Created 5 years ago
- Reactions:6
- Comments:59 (43 by maintainers)
(side note: I greatly appreciate the detailed issue writeup and the documented CodeSandbox example - thank you for that!)
To be clear from React’s point of view, a library “missing” an update is always a bug. No matter when an update happens, the end result should be consistent.