Photo by Tim Mossholder on Unsplash
How to create a private Redux store
A private Redux store is a good option if you're building a shared library or want to isolate parts of your application state with multiple stores.
Usually, when you see Redux in a React application it’s used as a single global store for application state. It tends to become a single dependency that creeps into every part of the app… but it doesn’t have to be that way. In this post, I’ll show you how you can create a small, isolated Redux store and control how the rest of the application accesses it.
Opting out of global context with react-redux
When you pass a store to the react-redux
<Provider />
component, your store is placed in a React Context object that is then made accessible to any child component that’s wrapped in a connect()
HOC or uses useSelector()
or useDispatch()
hooks.
import React from "react"
import { configureStore } from "@reduxjs/toolkit";
import { Provider } from "react-redux";
import { reducer } from "./reducer";
const store = configureStore({
reducer: reducer,
devTools: process.env.NODE_ENV !== "production",
});
export const MyStoreProvider: React.FC = ({ children }) => (
<Provider store={store}>
{children}
</Provider>
);
But there is a way to avoid this!
It’s not made clear in the react-redux documentation but you can pass your own context object to <Provider />
. When you do, your store is no longer accessible to connect()
, useSelector()
or useDispatch()
. This context object must contain the store and some initial state.
import React from "react"
import { configureStore } from "@reduxjs/toolkit";
import { Provider, ReactReduxContextValue } from "react-redux";
import { reducer, initialState } from "./reducer";
const store = configureStore({
reducer: reducer,
devTools: process.env.NODE_ENV !== "production",
});
type StoreState = ReturnType<typeof store.getState>
type StoreContext = ReactReduxContextValue<StoreState>;
const context = React.createContext<StoreContext>({
store: store,
storeState: initialState,
});
export const MyStateProvider: React.FC = ({ children }) => (
<Provider context={context} store={store}>
{children}
</Provider>
);
Controlling access to your store
Now we have a provider that puts a Redux store into our own context object, but how do we interact with the store?
With some undocumented react-redux
utilities:
createDispatchHook()
createSelectorHook()
These allow us to create custom hooks for accessing state and dispatching actions.
import { createDispatchHook, createSelectorHook } from "react-redux";
export const useDispatch = createDispatchHook<StoreState>(context);
export const useSelector = createSelectorHook<StoreState>(context);
You can go a step further and build hooks that perform specific interactions with your store.
import { createDispatchHook, createSelectorHook } from "react-redux";
const useDispatch = createDispatchHook<StoreState>(context);
const useSelector = createSelectorHook<StoreState>(context);
export const useCurrentUser = () => useSelector(s => s.currentUser);
export const useSetCurrentUser = () => {
const dispatch = useDispatch();
return (user: User) => dispatch({
type: "SET_CURRENT_USER",
payload: user
});
};
Devtools
If your application already has a Redux store you may want to give your store a custom name in Redux Devtools so you can easily distinguish it.
const store = configureStore({
reducer: reducer,
devTools:
process.env.NODE_ENV !== "production"
? { name: "My custom store" }
: false
});
Putting it all together
So altogether that’s:
import React from "react"
import { configureStore } from "@reduxjs/toolkit";
import {
Provider,
ReactReduxContextValue,
createDispatchHook,
createSelectorHook
} from "react-redux";
import { reducer, initialState } from "./reducer";
const store = configureStore({
reducer: reducer,
devTools:
process.env.NODE_ENV !== "production"
? { name: "My custom store" }
: false
});
type StoreState = ReturnType<typeof store.getState>
type StoreContext = ReactReduxContextValue<StoreState>;
const context = React.createContext<StoreContext>({
store: store,
storeState: initialState,
});
export const MyStateProvider: React.FC = ({ children }) => (
<Provider context={context} store={store}>
{children}
</Provider>
);
export const useDispatch = createDispatchHook<StoreState>(context);
export const useSelector = createSelectorHook<StoreState>(context);
Notice we’re not exporting our store (or the context that holds our store) so there’s no way for any other part of our application to access the store aside from calling our two hooks. Our Redux store has become private.