Manipulating state in Redux

Introduction

Manipulating objects in Redux might seem a little bit overwhelming at first but in time it becomes very clear and easy to understand. You just need to remember a few things and you can’t forget the crucial rule – don’t mutate the state directly. 

The goal of this article is to demonstrate how to manipulate nested objects and arrays in Redux so I won’t focus on things like setting the React project and making Redux configuration. I will not focus on redux actions as well. For the purpose of this article, I created a simple project on Github – a page with posts, comments and likes. 

https://github.com/wojciechnowaczyk/reduxApp

What you need to know: 

  • JS basics
  • React basics
  • Redux basics

Let’s go!

Mutating the state

Objects in Redux state are immutable, which means that values can’t be changed and to handle this situation we just need to create copies of elements with changed values. The object comparison is made with shallow compare, which means that only object references are being compared, not the whole structure. In this case adding, removing and changing fields don’t change the reference but the structure. 

Why should we avoid mutating the state? From the official Redux documentation:

  • It causes bugs, such as the UI not updating properly to show the latest values
  • It makes it harder to understand why and how the state has been updated
  • It makes it harder to write tests
  • It breaks the ability to use “time-travel debugging” correctly
  • It goes against the intended spirit and usage patterns for Redux

Incrementing numbers

The first and, in my opinion, the easiest case to show is creating an incrementing redux action. I created a state structure that looks like this:

What do we want to achieve? Our goal is to increase “likesCount” on click event. In this case we need to pass a post id  as an argument to the dispatch action We need to know which object we are going to change. 

The first thing we need to do is returning the whole state to avoid mutation – we want to change a single element, not overwrite the whole state, right? To do that, we just need to return “…state” (these free dots are the spread operator, you can read more about it here.
If we leave the code that way, the action won’t make any changes – there will be created a copy of the state with the same values (without any changes). But we want to change something! We want to add a like to likesCount. 

So the next step is going through the array of posts and finding the one we want to change based on the post id we passed in dispatch action. 

The last thing to do is incrementing the value. Firstly, we need to return the rest of the post elements with “…post”. The situation is similar to returning the whole state in the beginning. Now, we can access the attribute we want to change and increase the value by one. 

And that’s it. The likes counter should work fine. Remember that for the need of this article, I allow users to increase likesCount infinitely. In real life in most scenarios there should be the possibility to add just one like.

Decrement numbers

The way how the decrement works is similar to increment with one difference – we just need to subtract the value in likesCount. You can see it below:

Adding an object to an array

Now, let’s take a look at the comments and add new comments to the list. Our comment object looks like this: 

so if we want to add a new comment to a list we just need to add an object consistent with this pattern. 

Firstly, like in the previous cases we just need to return the {…state} to avoid mutating. We have a list of all the posts but we just want to change the comments of a single post. So what should we do? We just need to map through the whole posts array and compare the post id with the id passed in the action. If there is a match, then we can manipulate this object. The rest of the objects are returned without any changes. 

And now we have our post found, what next? The first thing we need to do is, like always, return the whole object {…post}, then we just type: “comments: post.comments.concat(newComment)”. New comment is the object that might be passed from the action or set in the reducer, basically it’s a new  comment object that contains id and text fields. What about concat? 

From the official documentation: 

“The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array.”. If you want to read more about this method, just visit the link.
And that’s it, we have a new comment added to the post! 

Removing object from an array

Removing comments is done in a slightly different way . To delete a comment we need the index of the element we want to delete. To achieve this, we need to get the object with the post that we want to manipulate, find the index of the comment we want to delete, and delete it using the slice method. 

To find an object we can use the find JS method: 

“The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.”. You can find more about this method here.

The, we take the object that find method returns and use the findIndex JS method: 

“The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise, it returns -1, indicating that no element passed the test.” More about this method here.

We need to find an object basing on the comment id compared to the id passed in the action; we pass the function inside the findIndex method: posts.comments.findIndex(comment => comment.id === action.payload.id).

When we get the index of the element we want to delete, we just need to use the slice method: 

“The slice() method returns a shallow copy of a portion of an array into a new array object selected from start to end (end not included) where start and end represent the index of items in that array. The original array will not be modified.”. More about the method here.

How are we going to create a new comments array? We just need to create a new array based on the previous one.
const newCommentsArray = […post.comments.slice(0, index), …post.comments.slice(index + 1)].

In short – we just join two arrays into one, apart from the one element which we want to delete. 

…post.comments.slice(0, index) – returns the array from the beginning to the last element before the index we want to delete.
…post.comments.slice(index + 1) – returns the array from the element one for the element we want to delete to the end of the array.

Summary

As you can see, updating objects in Redux is not as difficult as it looks. You just need to remember to not mutate the state and be very focused on the nested objects that you want to change – you just need to be sure that you are manipulating the right object. Obviously there are many approaches to how the structure of Redux should look like. There are different ways of changing the nested objects but I wanted to show only the most common one. If you want to simplify the redux actions, you can take a look at Immer – a library that simplifies the process of writing immutable update logic. To do so, you just need to import the produce function from Immer library, pass the current state and pass the changes that should be made. The library would take care of the immutability problem! The Immer library looks really helpful but it is also good to know what is happening inside the reducers!

Dodaj komentarz