Skip to content

<Provider> misses state changes that occur between when its constructor runs and when it mounts #1126

Closed
@rgrove

Description

@rgrove

Do 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions