Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
With Redux, we can use it to store data in a central location in our JavaScript app. It can work alone and it’s also a popular state management solution for React apps when combined with React-Redux.
In this article, we’ll look at how to access the Redux store with React-Redux.
Accessing the Store
With React-Redux, we can access the store by defining a mapStateToProps
function.
However, we can also access it in a customized way. Internally, React-Redux uses React’s Context API to make the Redux store accessible to deeply nested connected components.
Currently, React-Redux is normally handled by a single default context object instance generated by React.createContext()
called ReactReduxContext
.
Provider
uses ReactReduxContext.Provider
to put the Redux Store and the current state into context and connect
uses ReactReduxContext.Consumer
to read values and handle updates.
Providing Custom Context
We can provide a custom context to React-Redux via the context
prop.
For example, we can pass in our own context in the following code:
import React from "re
import ReactDOM from "react-dom";
import { connect, Provider } from "react-redux";
import { createStore } from "redux";
function count(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const store = createStore(count);
function App({ increment, decrement, count }) {
return (
<div className="App">
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<p>{count}</p>
</div>
);
}
const mapStateToProps = state => {
return { count: state };
};
const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const customContext = React.createContext();
App = connect(
mapStateToProps,
{ increment, decrement },
null,
{ context: customContext }
)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={store} context={customContext}>
<App />
</Provider>,
rootElement
);
In the code above, we have the usual React-Redux code for connecting the store to the App
component via connect
.
However, we create a customContext
via:
const customContext = React.createContext();
Then in the connect
call, we have:
App = connect(
mapStateToProps,
{ increment, decrement },
null,
{ context: customContext }
)(App);
The 4th argument is { context: customContext }
, which is where we set our custom context.
Also, we have:
<Provider store={store} context={customContext}>
<App />
</Provider>
to set our context
prop via context={customContext}
.
Multiple Stores
We can also use multiple stores with React-Redux by nesting providers as follows:
import React from "react";
import ReactDOM from "react-dom";
import { connect, Provider } from "react-redux";
import { createStore, compose } from "redux";
function countA(state = 0, action) {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
}
function countB(state = 0, action) {
switch (action.type) {
case "DECREMENT":
return state - 1;
default:
return state;
}
}
const storeA = createStore(countA);
const storeB = createStore(countB);
function App({ increment, decrement, countA, countB }) {
return (
<div className="App">
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<p>{countA}</p>
<p>{countB}</p>
</div>
);
}
const mapStateToPropsA = state => {
return { countA: state };
};
const mapStateToPropsB = state => {
return { countB: state };
};
const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const contextA = React.createContext();
const contextB = React.createContext();
App = compose(
connect(
mapStateToPropsA,
{ increment },
null,
{ context: contextA }
),
connect(
mapStateToPropsB,
{ decrement },
null,
{ context: contextB }
)
)(App);
const rootElement = document.getElementById("root");
ReactDOM.render(
<Provider store={storeA} context={contextA}>
<Provider store={storeB} context={contextB}>
<App />
</Provider>
</Provider>,
rootElement
);
In the code above, we defined 2 separate stores by writing:
const storeA = createStore(countA);
const storeB = createStore(countB);
where countA
and countB
are the reducers.
Then we nest our connect
calls within the arguments of the compose
function to combine the connects as follows:
App = compose(
connect(
mapStateToPropsA,
{ increment },
null,
{ context: contextA }
),
connect(
mapStateToPropsB,
{ decrement },
null,
{ context: contextB }
)
)(App);
We defined mapStateToPropsA
and mapStateToPropsB
to map states from the 2 stores before this call.
Then in the last argument of each connect
, we pass in an object with the context
that we want to use to connect the store to App
.
Then in the ReactDOM.render
call, we pass in:
<Provider store={storeA} context={contextA}>
<Provider store={storeB} context={contextB}>
<App />
</Provider>
</Provider>
to set the store
and context
we want to use for each provider. The context
prop of each Provider have to match with connect
.
Conclusion
We can access multiple stores by nesting multiple Providers
and providing a store and context for each.
The context is defined by calling React.createContext()
, which is part of the React Context API used to share data between components.
Then we can use the compose
function to add combine multiple connect
calls together. We pass into the 4th argument of connect
in each call.
We can also use the same method to customize access to a single store, except that we don’t need to call compose
.