Redux: Refactoring the Reducers

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 3 years ago

We will learn how to remove the duplication in our reducer files and how to keep the knowledge about the state shape colocated with the newly extracted reducers.

[00:00] Earlier, we removed the visibility filter reducer, and so the root reducer in the app now combines only a single todos reducer. Since index.js acts effectively as a proxy to the todos reducer, I will just remove index.js completely, and I will rename todos.js to index.js, thereby making the todos my new root reducer.

[00:24] The root reducer file now contains ById, all IDs, active IDs, and completed IDs, and I'm going to extract some of them into separate files. I'm starting with ById reducer, and I'm creating the file called ById.js, where I paste this reducer and export it as a default export.

[00:44] I'm also adding a named export for a selector called get todo, that takes the state and ID, where the state corresponds to the state of ById reducer. Now I'm going back to my index.js file, and I can import the reducer as a default import, and I can import any associated selectors in a single object with a namespace import.

[01:10] If we take a look at the reducers managing the IDs, we will notice that their code is almost exactly the same except for the filter value which they compare action filter to. I will create a new function called create list that takes filter as an argument.

[01:30] It returns another function, a reducer that handles the IDs for the specified filter, so its state shape is an array, and I can copy-paste the implementation from all IDs. I just need to change the all literal here to the filter argument to the create list function, so that we can create it for any filter.

[01:54] Now I can remove the all IDs, active IDs, and completed IDs reducer code completely. Instead, I will generate the reducers using the new create list function I wrote, and passing the filter as an argument to it.

[02:12] Next, I will extract the create list function into a separate file, just like I extracted the ById reducer. I added a new file called create list. I pasted my create list function there, and I will export it as a default export.

[02:30] Now that it's in a separate file, I'm adding a public API for accessing the state in form of a selector. That is called get IDs, and for now, it just returns the state of the list, but this may change in the future.

[02:45] Now I will go back to my index.js file, and I will import create list just like I usually import reducers, and I will import any named selectors from this file, as well.

[02:58] I'm renaming the IDs by filter reducer to list by filter, because now that the list implementation is in a separate file, I'll consider its state structure to be opaque, and to get the list of IDs, I will use the get IDs selector that it exports.

[03:16] Since I also moved the ById reducer into a separate file, I also don't want to make an assumption that it's just a lookup table, and I will use from ById, get todo selector that it exports and pass its state and the corresponding ID.

[03:33] This lets me change the state shape of any reducer in the future without rippling changes across the code base. Let's recap how we refactored the reducers.

[03:45] First of all, the todos reducer is now the root reducer of the application, and its file has been renamed to index.js. We extracted the ById reducer into a separate file.

[03:58] The ById reducer is now declared here, and it is exported from this module as a default export. To encapsulate the knowledge about the state shape in this file, we export a new selector that just gets the todo by its ID from the lookup table.

[04:17] We also created a new function called create list that we use to generate the reducers, managing the lists of fetched todos for any given filter.

[04:28] Inside createlist.js, we have a create list function that takes filter as an argument, and it returns a reducer that manages the fetched IDs for this filter.

[04:41] The generated reducers will handle the received todos action, but they will keep any action that has the filter different from the one they were created with.

[04:50] Create list is the default export from this file, but we also export a selector that gets the IDs from the current state. Right now, the IDs are the current state, but we are free to change this in the future.

[05:08] In index.js, we use the namespace import syntax to grab all selectors from the corresponding file into an object. The list by filter reducer combines the reducers generated by create list, and it uses the filters as the keys.

[05:26] Because list by filter is defined in this file, the get visible todo selector can make assumptions about its state shape and access it directly. However, the implementation of create list is in a separate file, so this is why it uses the from list get ID selector to get the IDs from it.

[05:48] Now that we have the IDs, we want to map them to the todos inside the state by ID. But now that it's in a separate file, rather than reach into it directly, we use the selector that it exports to access the [inaudible] todo.

[06:04] Redux does not enforce that you encapsulate the knowledge about the state shape in particular reducer files. However, it's a nice pattern, because it lets you change the state that is stored by reducers without having to change your components or your tests if you use selectors together with reducers in your tests.

~ 6 years ago

looks like a given component (or container, or equivalent) could ultimately have its own reducer, doesn't it ?

Dan Abramov
Dan Abramovinstructor
~ 6 years ago

If your components map 1:1 to reducers, maybe you don’t need Redux, and could use Redux state model instead :-). In this tutorial, the structure of components doesn’t neatly map to the structure of reducers. This has benefits such as ensuring entities are never duplicated in the state, but it also means components do not “have” their own reducers.

~ 6 years ago

Here you mention we write the selector function in the same file as the reducer. But if we were to make use of selector library like reselect, should the selector functions be written in a separate file? What would be a good way to go about it? Thanks