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 usescombineReducers
internally), 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_PAGE
action.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
pageName
andpageState
match 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
rootReducer
withcombineReducers
. Then we create thehydratedReducer
as 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 theaction
matches ourHYDRATE_PAGE
action then we modify the state by replacing the state for that slice with the one from our payload.