Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / Javascript
Article

A Redux State Organization Proposal

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
28 Nov 2018CPOL6 min read 3.4K  
A state organizational proposal for Redux that scales based on relational data

Introduction

When using Redux, it’s difficult to start organizing your state. I’ve seen many of my students adding properties on the go as they needed them. This wild strategy may lead you to have a terrible technical debt caused by a huge state.

For example, when following that strategy, you might end up having something like this:

Image 1

Try to imagine this, but with almost 50 properties, and larger lists.

The problem I find in the state above is that all properties are mixed.

  • auth_token will be used mainly in a centralized way: an ApiClientservice or middleware.
  • movies is a list of entities. It will be used across the entire app to render the information provided by the app.
  • fetching is a presentational flag, we’re going to use it in a specific view (Component) in order to show a loading spinner, for example.

We can see how the properties are mixed in terms of magnitude— because movies (data) don’t have the same level of importance as fetching (flag) -and usage - because they are used in completely different places and times.

These two points may affect design, a mixed up state organization may affect in the modularity of our selectors (mapStateToProps).

After some attempts, I came up with a state organization that solves the problem above. It ends up being quite flexible, fitting some projects with different structures.

Image 2

This state is created using combineReducers to define separate state parts to be controlled by different reducers. Note that I used Shorthand Property Names to define the properties of the object: authentication is a shorthand for authentication: authentication, being the second one, the reducer function that I’ll describe later.

The authentication reducer is meant to store and control all the data related to the currently signed in user. You can skip this part if you don’t have any authentication strategy in your app. In it, you can save things like the current user object, the auth token. This is really useful, for example, when you want to remember the session, since it can be persisted and preloaded when creating the store. Here’s a really cool tutorial by Dan Abramov explaining how to do that easily.

The reducer of this is going to listen to specific action types that are related to the authentication: SIGN_IN_SUCCESS SIGN_UP_SUCCESS LOG_OUT

This is the most special part of the reducer. We’re used to seeing reducers based on a huge switch over the action.type with a list of transformations for the state. But this one is going to control the entire set of data of our Front-End with less than 20 lines of code.

The idea is that this property of the state stores a local database with all the entities that we have to work with and that have been provided to us from the Back-End at some point. Let’s say we continue with the movies example, then this can be a possible state:

Image 3

As you can see, the state is separated into different entity groups, these groups aren’t arrays, but objects with keys related to the IDs of every object of every entity. This will avoid repetition. The main idea is that this has to work as a mini local relational database: nothing is repeated, but referenced. By avoiding nesting, we’re making it easier to relate data and to avoid inconsistency.

If your data is all local, you’ll have no problem to manage it this way, but if you’re consuming an API, most likely it will provide the data in a nested way. In order to process the data into our format, you can use normalizr. After some configuration, it will be able to flatten calling the function normalize . It will return an object with two properties: entities and result. Entities will be the data fetched and normalized in our format; Result will be the relation of IDs of the root entity that you just fetched.

Entities! What a coincidence, huh? Not really. Actually, I got this pattern from the Redux’s Real World Example which consumes Github’s API, normalizing its data after it.

Thanks to this format, we can write a reducer that controls the entire entities part of the state with just a few lines.

Image 4

Every time our action includes the property entities , it’s going to be merged with our current state’s entities value.

Warning: This reducer is only adding or overwriting data as it comes, there’s no case for deleting data from our state. Some may say it’s not needed as a delete can be seen as a modification on the delete flag of the element (soft deletion). What do you think? Any proposal to improve this case? Let’s debate it in the comments!

All our data is being handled automatically and merged into the state. We should be careful, though: our data is stored in objects, there are no arrays, so no order.

We should keep the order of the result provided by the Back-End, they might be spending a lot of resources (so money 💸) to decide the order in which the movies are presented to every user (Netflix, someone?). But by normalizing them, we’re ignoring that order. This is why normalizr gives us also the results, a relation of IDs representing the fetched resources.

You fetched a single movie with ID 101 (the API responds an object)? The results property will be 101; fetching an array of movies with IDs 101 and 100 (API sends an array)? results will be this array [101,100] (keeping the order). See an example below:

Image 5

Notice that the original response was an array of movies, and the results property of the normalized object is an array too, keeping the original order.

There’s no space in the entities reducer for this results array, so we have to set it into another place: the pages reducer. The pages reducer is all about the local variables that the different pages need in order to render its information. Arrays of elements, loading flags, maybe form-data if not using redux-form. The basic idea is that while the entities reducers is saving data from the resources, pages reducer stores data related to the rendering screen.

Image 6

As you can see, when mapping state to props in our containers, we’re creating the array mapping all elements in the result (state.pages.boxOffice.myListMovies, list of IDs) to the actual data in the entities .

The idea is to split the state.pages property again into a set of all the pages you’re showing in your app. For example, every component loaded by a route React Router.

This structure is going to help you scale your data-driven application easily. If you have to add other types of entities, it won’t affect the pages structure. If you want to add another screen that loads the existing entities in a different way, you can work with the pages reducer without bothering about data control. All of this will help us follow the simple source of truth principle, which will help you avoid many problems during your development.

In the future, I would like to write about Redux’s Reselect using this pattern.

Let me know what you think in the comments and let’s discuss better practices or modifications.

Happy coding!

- Arol Viñolas, Head of Studies (or CTO) @ Codeworks

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Writer Codeworks
Spain Spain
Are you ready to focus 11 hours a day, 6 days a week, for 12 weeks?
Codeworks is the leading JavaScript bootcamp in Europe, as consistently shown by student outcomes and reviews.

We give you solid software engineering foundations, and offer the top curriculum you deserve to become a successful programmer in your projects and career.

We’re based in Barcelona. Our classes are taught in English, in person.

We work with top companies to offer the best career opportunities to our students.

Visit the Codeworks website to find out more.
This is a Organisation (No members)


Comments and Discussions

 
-- There are no messages in this forum --