Initial state in a Redux app can be set in two ways:
- pass it as the second argument to
createStore
(docs link) - pass it as the first argument to your (sub-)reducers (docs link)
If you pass initial state to your store, how do you read that state from the store and make it the first argument in your reducers?
First let's consider a case where you have a single reducer.
Say you don't use
combineReducers()
.Then your reducer might look like this:
Now let's say you create a store with it.
The initial state is zero. Why? Because the second argument to
createStore
wasundefined
. This is thestate
passed to your reducer the first time. When Redux initializes it dispatches a “dummy” action to fill the state. So yourcounter
reducer was called withstate
equal toundefined
. This is exactly the case that “activates” the default argument. Therefore,state
is now0
as per the defaultstate
value (state = 0
). This state (0
) will be returned.Let's consider a different scenario:
Why is it
42
, and not0
, this time? BecausecreateStore
was called with42
as the second argument. This argument becomes thestate
passed to your reducer along with the dummy action. This time,state
is not undefined (it's42
!), so ES6 default argument syntax has no effect. Thestate
is42
, and42
is returned from the reducer.Now let's consider a case where you use
combineReducers()
.You have two reducers:
The reducer generated by
combineReducers({ a, b })
looks like this:If we call
createStore
without theinitialState
, it's going to initialize thestate
to{}
. Therefore,state.a
andstate.b
will beundefined
by the time it callsa
andb
reducers. Botha
andb
reducers will receiveundefined
as theirstate
arguments, and if they specify defaultstate
values, those will be returned. This is how the combined reducer returns a{ a: 'lol', b: 'wat' }
state object on the first invocation.Let's consider a different scenario:
Now I specified the
initialState
as the argument tocreateStore()
. The state returned from the combined reducer combines the initial state I specified for thea
reducer with the'wat'
default argument specified thatb
reducer chose itself.Let's recall what the combined reducer does:
In this case,
state
was specified so it didn't fall back to{}
. It was an object witha
field equal to'horse'
, but without theb
field. This is why thea
reducer received'horse'
as itsstate
and gladly returned it, but theb
reducer receivedundefined
as itsstate
and thus returned its idea of the defaultstate
(in our example,'wat'
). This is how we get{ a: 'horse', b: 'wat' }
in return.To sum this up, if you stick to Redux conventions and return the initial state from reducers when they're called with
undefined
as thestate
argument (the easiest way to implement this is to specify thestate
ES6 default argument value), you're going to have a nice useful behavior for combined reducers. They will prefer the corresponding value in theinitialState
object you pass to thecreateStore()
function, but if you didn't pass any, or if the corresponding field is not set, the defaultstate
argument specified by the reducer is chosen instead. This approach works well because it provides both initialization and hydration of existing data, but lets individual reducers reset their state if their data was not preserved. Of course you can apply this pattern recursively, as you can usecombineReducers()
on many levels, or even compose reducers manually by calling reducers and giving them the relevant part of the state tree.In a nutshell: it's Redux the one who passes the initial state to the reducers, you don't need to do anything.
When you call
createStore(reducer, [initialState])
you are letting Redux know what is the initial state to be passed to the reducer when the first action comes in.The second option you mention, applies only in case you didn't pass an initial state when creating the store. i.e.
function todoApp(state = initialState, action)
state will only be initialised if there was no state passed by Redux
I hope this answers your request (which I understood as initializing reducers while passing intialState and returning that state)
This is how we do it (warning: copied from Typescript code).
The gist of it is the
if(!state)
test in the mainReducer(factory) functioncombineReducers() do the job for you. The first way to write it is not really helpfull :
But the other one, that is equivalent is more clear :