So React-Redux upgraded to 6.0.0. I've spent some time to migrate our codebase. Here's a bit of what I've learned.
In this writeup I will cover the following topics about React-Redux v6:
- Using custom context
- Accessing the store
- Supporting multiple stores
This writeup does not cover the following topic, although they're also changes to React-Redux's API after v6:
- Replacing
withRef
withforwardRef
- Deprecated
createProvider()
Major Changes
The major implementation change of React-Redux v6 is that it migrates from using React's Legacy Context API to React's New Context API. It mainly affects how it access the store internally, and how it allows its user apps to access the store.
This means that if your app is only using React-Redux’s two major APIs <Provider />
and connect
, chances are it will just work.
Other changes include deprecating directly passing store as props to connected components, deprecating multiple stores via storeKey
, deprecating createProvider
, etc.
Here is a short list of libraries that were initially broken by React-Redux v6, and have released (or in beta phase) their newest support:
If you are using React-Router-Redux, this library has been deprecated and is no longer maintained in favor of Connected-React-Router. You may refer to Connected-React-Router’s doc for reference on migration.
Providing Custom Context
Instead of using the default context instance from React-Redux, you may supply your own context object.
<Provider context={MyContext} store={store}>
<App />
</Provider>
If you supply a custom context, React-Redux will use that context instance instead of its default one.
Note that with React’s new context API, while it is possible to nest <Context.Provider />
, the provided value to the nearest ancestor provider will be used. Values provided in earlier ancestors will not be consulted or merged. This means you are not supposed to nest your custom context’s provider beneath React-Redux’s More explanations about the context API can be found here. <Provider />
. It will break React-Redux’s usage.
Note: I later learned about this issue where shadowing with nesting context's provider is a legit use case, and in that case a brilliant solution. I guess I should not have said something along the lines of "you are not supposed to..."
After you’ve supplied the custom context to <Provider />
, you will also need to supply this context instance to all of your connected components:
export default connect(mapState, mapDispatch, null, {
context: MyContext
})(MyComponent);
// or
const ConnectedComponent = connect(mapState, mapDispatch)(MyComponent);
<ConnectedComponent context={MyContext} />
Not providing a context to connected components will result in runtime error:
Invariant Violation
Could not find "store" in the context of "Connect(MyComponent)". Either wrap the root component in a , or pass a custom React context provider to and the corresponding React context consumer to Connect(Todo) in connect options.
Here's our async inject reducer in a CodeSandbox: Asynchronously inject reducer using React-Redux v6 and custom context.
Accessing the Store
Grabbing the store from context or from importing other files seems to have never been recommended by the library's maintainers. Nevertheless, it can be quite common anyway.
It's an anti-pattern to interact with the store directly in a React component, whether it's an explicit import of the store or accessing it via context.
In v6, React-Redux no longer uses React’s Legacy Context API. Instead, it uses React’s New Context API. This means the old way of accessing store by defining contextTypes
won’t work.
React-Redux exports the default context instance it uses for <Provider />
so that you may access the store by doing this:
import { ReactReduxContext } from 'react-redux'
// in your connected component
render() {
return (
<ReactReduxContext.Consumer>
{({ store }) => <div>{store}</div>}
</ReactReduxContext.Consumer>
)
}
I have forked the last CodeSandbox example with a cleaner implementation: Asynchronously inject reducer with React-Redux v6 using default context.
Supporting Multiple Stores
Once again using multiple stores is never recommended neither. The whole Redux v.s. Flux discussion seem to have drawn a clear line:
The original Flux pattern describes having multiple “stores” in an app, each one holding a different area of domain data. This can introduce issues such as needing to have one store “waitFor” another store to update. This is not necessary in Redux because the separation between data domains is already achieved by splitting a single reducer into smaller reducers.
Specifying multiple stores and accessing them with storeKey
is deprecated in v6. However, it is possible to implement it by providing (multiple) custom context, and have different stores live in different contexts.
// a naive example
// there is no need to supply a default value when creating the context
// the value will be supplied when React-Redux mounts with your Context.Provider
const ContextA = React.createContext();
const ContextB = React.createContext();
// assuming reducerA and reducerB are proper reducer functions
const storeA = createStore(reducerA);
const storeB = createStore(reducerB);
// rendering
return (
<Provider store={storeA} context={ContextA}>
<Provider store={storeB} context={ContextB}>
<App />
</Provider>
</Provider>
);
It is possible to chain connect()
import { compose } from 'redux';
import { connect } from 'react-redux';
compose(
connect(mapStateA, null, null, { context: ContextA }),
connect(mapStateB, null, null, { context: ContextB })
)(MyComponent);
CodeSandbox example: A reading list app with theme using a separate store, implemented by providing (multiple) custom context.
From a development experience perspective, I feel that the new context API provides a clearer isolation for multiple stores. Perhaps it can be less inadvisable at this time?
Links and References
And some issue threads
- React-Redux v6 feedback thread, #1083
- Access store from context? #1123
- Value is Undefined in SSR of React-Redux v6, #1107
- Since upgrading to v6.0.0, components connected via connectAdvanced are rerendering with unrelated action dispatch, #1118
<Provider>
misses state changes that occur between when its constructor runs and when it mounts #1126- Update docs for using a different store key, #1132
There are plenty of places to get help
Top comments (1)
Hi I upgrade the react project after upgrading the value of
this,context.store is undefined in injectSaga.js pls suggest me .