Skip to content
Hou - Engineer & Tech Educator

Understanding Normalized Data Stores in Redux

React, Redux, normalized-data-stores3 min read

Introduction

As an application grows, managing data can become a significant challenge. Redux provides a predictable and structured way to manage application state by keeping the entire state of your application in a single store. One way to improve the efficiency of state management is by using a normalized data store.

In this blog post, we'll explore what a normalized data store is, when to use it, and how to implement it in Redux.

What is a normalized data store?

A normalized data store is a way of organizing your application data in a way that optimizes performance and simplifies state management. Normalization involves dividing your data into separate entities and storing each entity in its own slice of the Redux store. Each entity can then be linked together using unique identifiers, such as primary keys.

Why/When to use normalized data stores in Redux?

As an application grows and becomes more complex, the amount of data that needs to be managed can quickly become overwhelming. When you use a normalized data store, you can break down complex data structures into smaller, more manageable pieces. This makes it easier to add, modify, or delete data without affecting other parts of your application.

Using normalized data stores in Redux has several benefits, including:

  • Reduced Redundancy: Storing related data in separate entities allows you to avoid repeating the same data in multiple places, reducing the amount of redundant data in the store.

  • Improved Performance: By breaking up data into smaller, related entities, you can improve performance by reducing the amount of data that needs to be updated when a change occurs.

  • Easier Updates: When updating data in a normalized data store, you only need to update the relevant entity slice, rather than searching through the entire store for the data to update.

  • Simplified Relationships: By using unique identifiers to relate entities, you can simplify relationships between data and make it easier to manage data relationships across your application.

How to use normalized data stores in Redux?

Let's say we have a simple blog application that allows users to create and edit posts. In a traditional Redux store, we might store the posts in a single array:

1const posts = [
2 {
3 id: 1,
4 title: "My first blog post",
5 content: "This is my first blog post.",
6 author: "John Doe",
7 },
8 {
9 id: 2,
10 title: "My second blog post",
11 content: "This is my second blog post.",
12 author: "Jane Smith",
13 },
14 // ...
15];

However, as the application grows and we introduce comments, tags, and other entities, it becomes more challenging to manage data in this format. Instead, we can normalize the data by storing each entity in its own slice of the Redux store.

A slice of the Redux store is a portion of the global application state that is responsible for managing a specific piece of data. The concept of a slice is central to Redux's design philosophy, as it encourages breaking up the application state into smaller, more manageable pieces.

For example, if we're building a blog application, we might have slices for posts, comments, and users. Each slice would contain all of the information necessary to manage that particular piece of data. The posts slice would contain all of the blog post information, such as the title, content, and author, while the comments slice would contain information about each comment on a blog post, such as the comment text and the author.

By breaking up the application state into slices, we can manage each piece of data separately, which can make it easier to update, retrieve, or delete data. This can also help to avoid unnecessary re-renders, as we can update only the relevant slice of the state when changes occur, rather than updating the entire state object.

The slices for the post, comment, and tag entities could look like the following:

1const posts = {
2 byId: {
3 1: {
4 id: 1,
5 title: "My first blog post",
6 content: "This is my first blog post.",
7 author: "John Doe",
8 },
9 2: {
10 id: 2,
11 title: "My second blog post",
12 content: "This is my second blog post.",
13 author: "Jane Smith",
14 },
15 // ...
16 },
17 allIds: [1, 2, /* ... */],
18};
19
20const comments = {
21 byId: {
22 1: {
23 id: 1,
24 postId: 1,
25 content: "Great post!",
26 author: "Mary Johnson",
27 },
28 2: {
29 id: 2,
30 postId: 1,
31 content: "I disagree with some of your points.",
32 author: "Bob Smith",
33 },
34 // ...
35 },
36 allIds: [1, 2, /* ... */],
37};
38
39const tags = {
40 byId: {
41 1: {
42 id: 1,
43 name: "JavaScript",
44 },
45 2: {
46 id: 2,
47 name: "React",
48 },
49 // ...
50 },
51 allIds: [1, 2, /* ... */],

Now that we've organized our data in a normalized data store, let's see how we can create, update, or delete entities in this format.

Creating a new entity

To create a new post, we need to generate a unique identifier for the post, such as a UUID, and add it to the byId object in the posts slice of the Redux store. We also need to add the new post's ID to the allIds array. Here's an example implementation:

1const addPost = (state, action) => {
2 const { id, title, content, author } = action.payload;
3 return {
4 ...state,
5 byId: {
6 ...state.byId,
7 [id]: { id, title, content, author },
8 },
9 allIds: [...state.allIds, id],
10 };
11};

Updating an existing entity

To update an existing post, we can simply update the post object in the byId object in the posts slice of the Redux store. Here's an example implementation:

1const updatePost = (state, action) => {
2 const { id, title, content, author } = action.payload;
3 return {
4 ...state,
5 byId: {
6 ...state.byId,
7 [id]: { id, title, content, author },
8 },
9 };
10};

Deleting an entity

To delete a post, we need to remove the post object from the byId object in the posts slice of the Redux store. We also need to remove the post's ID from the allIds array. Here's an example implementation:

1const deletePost = (state, action) => {
2 const { id } = action.payload;
3 const { [id]: deletedPost, ...remainingPosts } = state.byId;
4 const remainingIds = state.allIds.filter((postId) => postId !== id);
5 return {
6 ...state,
7 byId: remainingPosts,
8 allIds: remainingIds,
9 };
10};

Review

In this blog post, we've explored what a normalized data store is, why and when to use it, and how to implement it in Redux. By using a normalized data store, you can improve the efficiency of your state management and simplify your application's data structure. This makes it easier to add, modify, or delete data without affecting other parts of your application and reduces the amount of work that needs to be done by React. With these benefits in mind, consider using a normalized data store in your next Redux project.