I've got a state in my Redux store shaped like this:
type RootState = {
PAGE_A: StatePageA,
PAGE_B: StatePageB,
PAGE_C: StatePageC,
// AND SO ON...
}
Each page is a slice created with createSlice from @reduxjs/toolkit
I'm using Next.js static generation and SSR, so I need to hydrate my previously created store with the new state that comes form subsequent pages from the Next.js server.
Whatever page I'm routing to, I'll get the pre-rendered pageState, which should by of type StatePageA | StatePageB | StatePageC.
When that new state comes, I need to dispatch() a HYDRATE_PAGE action, which should replace the current page state.
This was my first idea on how to handle the HYDRATE_PAGE action.
export const HYDRATE_PAGE = createAction<{
pageName: "PAGE_A" | "PAGE_B" | "PAGE_C",
pageState: StatePageA | StatePageB | StatePageC
}>("HYDRATE_PAGE");
Then I would have to listen for it in every slice:
// EX: PAGE_A SLICE
// THIS WOULD HAVE TO BE DONE FOR EACH SLICE ON THE extraReducers PROPERTY
extraReducers: {
[HYDRATE_PAGE]: (state, action) => {
if (action.payload.pageName === "PAGE_A")
return action.payload.pageState
}
}
Is there a way where I can handle it in a centralized manner, without having to listen for that action in every slice reducer?
For example: a rootReducer kind of thing where I could listen for the HYDRATE_PAGE action and handle it in a single place like.
// THIS REDUCER WOULD HAVE ACCESS TO THE rootState
(state: RootState, action) => {
const {pageName, pageState} = action.payload;
state[pageName] = pageState;
};
Is this possible somehow?
Extra
There is this package called next-redux-wrapper, but I don't intent to use it. I'm trying to build my custom solution.
Overview of Concept
I'm not sure that your store has the best design because generally you want data to be organized by what type of data it is rather than what page it's used on. But I'm just going to answer the question as it's presented here.
A reducer is simply a function that takes a state and an action and returns the next state. When you combine a keyed object of slice reducers using
combineReducers(orconfigureStore, which usescombineReducersinternally), you get a function that looks like:We want to create a wrapper around that function and add an extra step that performs the
HYDRATE_PAGEaction.Typing the Action
The typescript types that you have in your action creator aren't as good as they can be because we want to enforce that the
pageNameandpageStatematch each other. The type that we actually want is this:Which evaluates to a union of valid pairings:
Modifying the Reducer
First we will create the normal
rootReducerwithcombineReducers. Then we create thehydratedReduceras a wrapper around it. We callrootReducer(state, action)to get the next state. Most of the time we just return that state and don't do anything to it. But if theactionmatches ourHYDRATE_PAGEaction then we modify the state by replacing the state for that slice with the one from our payload.