Possibly the most important lesson I’ve learned while building applications with React + Redux combo is to keep the Redux state object as shallow as possible. If only I had known this since beginning, I would have saved so much time and patience not having to go through extensive refactoring ever so often. Now I want to share what I’ve learned on how to manage the state like a boss.
This post is the first of two parts. In this post I’ll describe some benefits of a shallow Redux store. In part two, I’ll show how to design reducers and components to update and use the state efficiently.
The core of Redux is a single store that contains all of the application state. I won’t go into detail on why this is such a good paradigm, I think there’s enough information on that available already.
Let’s use a social media application as an example. We want to store posts, comments, users and related information fetched from the backend and display the information in a feed, profile pages and other occurences. Le’t say our backend sends the following data to display on the main feed page.
The server returns posts with all the data associated with them, including author, comments and the authors of comments. A naive approach to storing this data would be to write it to the state exactly like this. Then we could pass it all to a
<Feed /> component, which would pass each individual post to a
<Post /> component, which would pass the author data to an
<Author /> component and so on.
Initially this might seem like a good idea. It’s the simplest thing to do and simple is good, right? Looking at the JSON closely, one can see duplication of data. The same user with ID 2 is both the author of a post and a comment. This won’t initially cause any problems, but suppose we want to for example unfollow the user (his posts seem quite spammy, so that would be appropriate). We would have to update the
is_followed attribute for multiple instances of the same user. Not good!
Another problem wel’ll run into is displaying the data on other pages. Let’s assume we have a profile page where we want to display user info and all the posts from the user along with some user info. We can’t really get the basic info anywhere easily as it’s buried in the post and comment objects. We make a new request to load the users posts and store them somewhere else in the store.
Now, let’t improve the structure of the store and see how we can benefit from this. After receiving the feed data, we form the data into the following structure.
Thw data is now split into several sub-objects containing different types of data. We use objects and index the data by IDs. We can then reference the data simply by the id. There is now no duplication in the store. All the data is unique and is simply referenced in multiple places when needed.
Let’s see what we accomplished by the refactoring. If we now unfollow that spammy guy (we only followed him because we kinda know him from somewhere and they followed us and we didn’t want to seem rude), the status is automatically updated on all instances of the data, as they all rely on the same author object. We have a single source of truth and can count on it containing the latest information available.
Also, if we visit a profile page (of some more interesting person), we can grab the basic user data from the
authors part of the state if we have fetched a post or comment from the person already. This means that we can display some data to the user before receiving any new data from server and thus provide a better user experience. When we load the user page feed, the posts are placed in the same
posts section of the state. If a post already exists, we overwrite it and thus have the most recent state also on the main page!
Let’s sum up the benefits of a shallow store with no duplication of data
- same data can be easily utilized in different pages and components
- updated data is easily propagated to all components and pages using it
- no duplication means smaller store – handling the state will be more efficient and storing the state for later use in localStorage will require less space
In part two, we shall see how to manage the state efficiently with well designed reducers and components. Stay tuned!