Let's consider the following snippet:
const initialState = {
options1: [],
options2: [],
input1: "Your text",
input2: "Your text",
}
function store(state = initialState, action) {
...
}
I want to hydrate this store with options1 and options2 with values from the server, but at the same time I want input1 and input2 to be merged from the initialState object.
It would've been easy to subscribe to the '@@INIT' action and do the initialization I need, but since it's been explicitly said (#186) that this is an anti-pattern I've ruled this option out.
Two other options I've come up with:
1) Check whether input1 and input2 are defined:
function store(state = initialState, action) {
if(state.input1 == null && state.input2 == null) {
state = {...initialState, ...state}
}
...
}
But this is very hacky and someone else is going to ask why this check is there.
2) Modify initial state that comes from the server before passing it as a second argument to createRedux.
Probably better, but I'd like to keep initial state in the reducer's module.
What is the preferred way to achieve what I want? I keep thinking that there should be something like '@@HYDRATE' action where we can do initialization.
We're firing an action as soon as the store is created for hydration which reads from sessionStorage, localStorage or other services to retrieve information.
@merk Do I understand correctly that you do something like this:
const redux = createRedux(stores)
const hydro = collectHydro();
redux.dispatch({type: 'HYDRATE', hydro});
Or do you still pass hydrated state a second argument to createRedux?
My understanding of the second argument to createStore is for state serialized from the server. The kind of information we hydrate is authentication tokens and things that should persist across reloads, nothing more.
But yes, thats basically what we're doing.
Indeed, the state from server should be passed as second argument to createStore (createRedux in 0.12 API but you should update).
It will be merged with the initial state of your reducers ("stores" in 0.12) specified as default parameters.
@gaearon
Sorry, but state from server seems to replace state, not merge with initial state of my reducer.
E.g.:
function myReducer(state = {bar: "bar"}, action) {
...
}
store = composeStores({myReducer});
createRedux(store, {myReducer: {foo: "foo"}})
On next action dispatch state of myReducer won't have bar property.
Do you want to combine parts of state from server with initial state? I assumed you wanted to prefill some reducers, but not the others.
In this case don't use ES6 default parameters and do this manually, or via a custom "hydrate" action. Both options are fine.
const initialState = {
options1: [],
options2: [],
input1: "Your text",
input2: "Your text",
}
// We don't call them stores anymore! Check out 1.0 API and terminology.
// http://gaearon.github.io/redux
function reducer(state, action) {
// I want to hydrate this reducer's state with options1 and options2 with values from the server
// but at the same time I want input1 and input2 to be merged from the initialState object.
if (typeof state === 'undefined') {
// Server didn't supply any state. Use local state.
state = { ...initialState, hydrated: true };
} else if (!state.hydrated) {
// Server supplied state, but it's not merged with our local initial state yet.
state = { ...initialState, ...state, hydrated: true };
}
// do something
return state;
}
Another way of writing the same code:
const initialState = {
options1: [],
options2: [],
input1: "Your text",
input2: "Your text",
}
function reducer(state = initialState, action) {
if (!state.hydrated) {
state = { ...initialState, ...state, hydrated: true };
}
// do something
return state;
}
@gaearon Thanks, adding an extra flag seems to be the best way! Really appreciate your help.
I feel like merging state should be the default.
Meaning reducers should be able to pre-fill state with default and then use the server state to override default state.
Definitely surprised me that this was not the default behavior
Meaning reducers should be able to pre-fill state with default and then use the server state to override default state.
This is exactly how it works when you use combineReducers().
@gaearon Are you saying to put the server state in combineReducers instead of in createStore like the examples show?
Are you saying to put the server state in combineReducers instead of in createStore like the examples show?
The code is in this thread is from the times when Redux 1.0 wasn’t even a thing 😄 .
This works fine today:
const a = (state = 5, action) => state
const b = (state = 10, action) => state
const reducer = combineReducers({ a, b })
const store = createStore(reducer, { a: 42 })
console.log(store.getState()) // { a: 42, b: 10 }
Also, I wrote above that merging “just works” when you use combineReducers(): https://github.com/reactjs/redux/issues/433#issuecomment-129174253.
@roganov wanted something more sophisticated:
Sorry, but state from server seems to replace state, not merge with initial state of my reducer.
By default combineReducers() will merge only top-level object. If you want deeper merging, you need to implement it by hand because Redux can’t guess what kind of deeper merging you want. Still, you can use combineReducers() multiple times to make it automatic.
You sure about that?
Yes, I am sure about that. 😉
I have a similar problem, please consider this scenario:
const a = (state = {x:5, y:2}, action) => state
const b = (state = 10, action) => state
const reducer = combineReducers({ a, b })
const store = createStore(reducer, { a: {x: 9}, b: 11 })
console.log(store.getState()) // { a: {x: 9}, b: 11 }
in this example I get { a: {x: 9}, b: 10 } but what I want to get is { a: {x: 9, y:2}, b: 11 }.
then, I not found a better way to do this more like:
store.getState() to get the initial state of the store, combine it with the new hydrate object and call an action that replace the whole store with the new object.I not like any of this options, suggestions?
Most helpful comment
Another way of writing the same code: