Designing State In Redux

Designing State In Redux

Redux is a predictable state container with its roots from the flux architecture. In Redux, all data is made available at one place(single source of truth), so designing the state fairly well will help to scale.

Historically we’ve all used different flavors of MVC patterns that prefer nesting data to normalization. It suits well in the case of MVC, where the data is distributed across models which are represented by respective views. With the same mindset, it feels comfortable to follow the same pattern even with the Redux. At any moment if it feels more effort is needed to do trivial tasks in Redux, we would end up with the solution similar to normalizing the state, which I was not so comfortable. But I gave up after using Redux for a while, for good.

To know if it really helps, Let’s try building a Kanban app(aka trello) and check if normalization really helps as it promises. Don’t worry if you are not aware of the app, it is a simple app that has lists(tasks) inside the lists(lists).

sample kanbansample kanban

We have a list of lanes each having a collection of tasks and also the details of the user created the task. Keeping all the data hierarchically results in a state like this.

[
  {
    "lane_name": "todo",
    "lane_id": 122,
    "created_at": "now()",
    "tasks": [
      {
        "task_name": "use kanban",
        "task_id": 1,
        "created_by": {
          "user_name": "ash",
          "id": 70
        }
      },
      {
        "task_name": "try kanban tool",
        "task_id": 2,
        "created_by": {
          "user_name": "har",
          "id": 71
        }
      }
    ]
  },
  {
    "lane_name": "doing",
    "similar to": "above lane"
  }
]

Now let solve some common use cases and see how we end up with a state.

The very common downside with nesting is the duplication of the data. For example, in the above state if the same user has created two tasks we end up storing the user details twice. We face bigger problems when we want to update the user info, where we have to iterate(yuck!) through all the state and update the user details. Now, we solve this problem by moving the user to the top-level and having reference to the user using ID.

{
    "lanes": [
        {
            "lane_name": "todo",
            "lane_id": 122,
            "created_at": "now()",
            "tasks": [
                {
                    "task_name": "use kanban",
                    "task_id": 1,
                    "created_by": 70
                },
                {
                    "task_name": "try kanban tool",
                    "task_id": 2,
                    "created_by": 71
                }
            ]
        },
        {
            "lane_name": "doing",
            "similar to": "above lane"
        }
    ],
    "user": [
        {
            "user_name": "ash",
            "id": 70
        },
        {
            "user_name": "har",
            "id": 71
        }
    ]
}

For now, It is for sure that a task belongs to a specific lane and there is no problem of duplication. But it is a good idea to be planned for the future. Let’s try adding some features where the user can share a card to another lane. And also it is good to have a notification when the tasks are created on the board. You see the pattern here all of the features we are adding can be done without much effort when the state is normalized. Now we make the state completely normalzied and check if it solves all the problems.

{
    "lanes": [
        {
            "lane_name": "todo",
            "lane_id": 122,
            "created_at": "now()",
            "tasks": [
                1,
                2
            ]
        },
        {
            "lane_name": "doing",
            "similar to": "above lane",
            "tasks": [
                1
            ]
        }
    ],
    "tasks": [
        {
            "task_name": "use kanban",
            "task_id": 1,
            "created_by": 70
        },
        {
            "task_name": "try kanban tool",
            "task_id": 2,
            "created_by": 71
        }
    ],
    "user": [
        {
            "user_name": "ash",
            "id": 70
        },
        {
            "user_name": "har",
            "id": 71
        }
    ]
}

By adding the id of the task to an array in the lane we can share the task between the lanes, so easy. Also, having dedicated table for the tasks helps to show notifications easily without iterating across all the lanes(just show the recent one from the tasks array).

In SPA(single page applications) usually, we create the replica of the database in the Redux. Design front end state in analogous to the relational databases. Every table can be compared to an individual object in the Redux.

Conclusion

Remember, there is no rule that normalziation always performs better than nesting. You just have to choose the correct method for the problem. With SPA gaining more traction than ever it makes more sense to store the data as we do in the relational database.So that we can consume the normalized data in the way we need, else we would end up doing a lot of iterations to achieve the same.