In the last lecture, we outsourced our action types,
there is something else we do a lot as our application grows and that is use multiple reducers.
Now I did say that we only have one reducer with redux and this is the case, all actions in the end get
funneled through one reducer but redux, the package gives us a utility method we can use to combine
multiple reducers into one, so that we still follow the pattern of having only one reducer behind
the scenes
but for us as a developer, that we can split up our code logically so that we dont get one huge reducer,
imagine how this reducer would grow as we add more and more action types we want to handle but that
we can split it up by feature. In our tiny demo application,
it might make sense to have a reducer which handles the counter and one which handles the results, even
though they're somehow related,
they technically are different or they manage different parts of the app so splitting it up might make
sense
and this is exactly what we're going to do.
So let's split our reducer here into two reducers, one for the counter related parts of the switch case
statements and one for the results related one.
So for that I'll simply add here in my store folder, I'll add a new reducers folder and then I'll add a
file which I'll name counter.js and one which I'll name result.js
Now I'll copy all the code from my reducer.js file and put it into the counter.js file,
I need to adjust the path to my actions file now by simply going up one level and then here, we are
in the folder where the action.js file lives.
The initial state
now here in this file no longer needs to know about the results array, it's just about the counter here, in
the counter.js file,
the reducer function still looks as before, state which is assigned the initial state and we receive
an action,
I just get rid of the result related cases here to make this a bit slimmer,
this already is my finished reducer.
We can name this here counterReducer, this function,
we can also leave it named reducer, whatever you like.
Now I'm again going to copy the code from the reducer.js file and put it into my results.js
file, again adjust the path to the actions file and now I'm going to get rid of the counter property in
my initial state. The reducer then still stays as it is, but I remove all counter related cases so that
only the store related ones are there.
Notice that I still access state.Counter in here because in the end, the two reducers are going to
get merged together into one global state
so there still is going to be a counter state in there,
we still might need to update this later but we'll see.
Then I'll still export my reducer here
and this allows me to get rid of my reducer.js file in the store folder.
Now of course, in index.js,
I no longer have a valid import
here, this reducer doesn't exist,
instead I now have two reducers,
so let's import them.
Let's go into the store folder, into the reducers folder and import counter.js and I'll name this
counterReducer here,
I'll duplicate the import to then also import the result reducer from the store
reducers results.js file.
Now to combine them, I need to import a helper function from the redux package,
it's called combineReducers, as the name suggests,
this is a function which takes a javascript object mapping our reducers to different slices of our
state as input and merges everything into one state and one reducer for us.
So here before creating the store, I'll create a new constant which I'll name rootReducer, the name is up to
you though and I'll call combinedReducers.
Now as I just said, this function takes a javascript object as an input and there, we now simply can create
sections of our app so to say, any name you want like the counter or just ctr, whatever you want,
I'll name it ctr here here and map the counter reducer to it and then I'll add my res area with
the resultReducer.
So now we're telling redux in the end, hey I got two different feature areas in my application,
ctr and res,
please use these reducers for each of them and merge everything together into one store, into one state,
into one reducer.
If we now pass the rootReducer here to create store and save all the files, we shouldn't get an error
down there
when npm start runs. In the application though, if we reload it, we will get an error
that map can't be executed on undefined
and basically here, the problem is that stored results where we map through all the stored results, that
this won't work because this props stored results now refers to undefined and not to an array of results
anymore.
This happens due to us combining reducers,
now we will have one state in the end but to avoid naming conflicts, redux adds one level of nesting
where it has one state object
but basically with these keys here, in combined reducers as properties which give us access to these sub
states for these feature areas, you could say. So in the counter.js file, if we want to access the counter
we have to access the global state.ctr.counter, .ctr
since this is the name we gave this slice of our global state and for the result, it would be .res.
So here, it's state.res, like this.
Now if we save this, the application is not broken anymore
and if I now click a button it works, the same for storing results
though here we see it adds results but somehow it's not really adding them,
not really displaying them here.
The reason for this is that the stored result actually is undefined because in the resultReducer, we do
store a snapshot here for state.Counter and this doesn't work because this now, state.Counter, is not known
because this local initial state in this reducer doesn't have a counter property.
Now you could think that you simply add state.ctr.counter, if you do this and save everything, click
add 10 and click store result, you get an error though that you can't read property counter of undefined.
The reason for this is that inside this reducer function, it basically has no access to the global state
only to that state of that reducer function.
That's different than the counter component where we connect our react component to the global state,
there we can access the different pieces of the state through our slices
we set up in index.js
.
This does not work inside of the reducers.
So if we are in a reducer where we need to get a value from the global state, we should simply get it
as an action payload
and this is generally how your reducers work anyways
most of the time, it's old state plus action plus optionally action data and you return a new state.
So here I expect to get that same result property on my incoming action and to receive that, I need to
adjust my counter.js file. There where I execute on store
result, here I in the end just need to get my result
so I expect to get my result here as an argument and then I can simply pass this along with the type, use
the key
you chose to use here,
action result.
So we should have a result property here
too in the object we dispatch, we bind or we set the value to the result argument we expect to get here
and now to get it just like before with deleting, we execute this here on the button as an anonymous
function so that we can pass some data here on store result, should now be
this.props.ctr gives us access to the counter
and that's actually the value we want to store.
Now if we go back and I click add 10, I can store 10 again, by the way if you hit store result really
fast after each other, you will get this error simply because the ID we assign is the current date
and if you hit this very rapidly, there will simply be the same date assigned twice which leads to this
error,
it's of course just a temporary ID solution here, not one you would use in production,
so for me here it doesn't matter.
The core takeaway is how you can split your reducers into different slices and how you then still make
sure you get the data you need in each of these slices.