Angular 1.x Redux: Avoid State Mutation with Immutable Operations

Lukas Ruebbelke
InstructorLukas Ruebbelke
Share this video with your friends

Social Share Links

Send Tweet

One of the biggest mental shifts that developers have to take when embracing redux is shifting from mutable operations to immutable operations. The problem with mutable operations is that they create side effects that involve anything else that is referencing the thing that is being mutated.

With immutable operations within a reducer, a new object is returned for every operation – completely bypassing the possibility that an existing reference is going to be compromised. It is then up to the consumer to decide how they want to get the new data and what to do with it.

In this lesson, we are going to flesh out the CRUD functionality of our bookmarks reducer, doing it first using mutable operations and then refactoring it to use immutable operations.

[00:00] One of the biggest mental shifts that developers have to take when embracing Redux is shifting from mutable operations to immutable operations. The problem with mutable operations is that they create side effects that involve anything else that is referencing the thing that is being mutated.

[00:19] With immutable operations within a reducer, a new object is returned for every operation, completely bypassing the possibility that an existing reference is going to be compromised. It is then up to the consumer to decide how they want to get the new data and what they want to do with it.

[00:38] In this lesson, we are going to flesh out the CRUD functionality of our Bookmarks Reducer, and do them first using mutable operations, and then refactor them to use immutable operations.

[00:51] We're going to start by laying the groundwork in our Bookmarks State file by defining some action constants. The first action constant that we're going to create is Create Bookmark, and we'll go ahead and create one for Update Bookmark, and then let's do Delete Bookmark as well.

[01:18] From here, let's go ahead and extend our bookmarks actions creator. We're going to build out the Delete Bookmark method, and this is just going to return an action object with the type Delete Bookmark and a payload of Bookmark.

[01:40] We will build out the Save Bookmark method, and that's going to also accept a bookmark. From here, we're going to create a variable to test that we have an ID. Are we working with an existing bookmark, or a new one? Based on the presence of that ID, we are either going to do an Update Bookmark type or a Create Bookmark Action type.

[02:06] If there's not a bookmark, we need to go ahead and add one. We're going to simulate the back end by setting the ID on the bookmark using the Unique ID method off of Lodash, and we're going to seed that with 100. That's temporary, just to simulate calling the back end, and then we're going to return an action object with the type that we created above and the payload of Bookmark.

[02:38] Let's go ahead and import this method from Lodash. Import Unique ID from Lodash, and then we'll go ahead and add these methods to our return object. Save Bookmark, and Delete Bookmark. Now that these are defined, let's hop into our bookmarks controller and build out these two methods here.

[03:09] This.store.dispatch. We're going to call Bookmarks Action Save Bookmark, and being that the shape is nearly identical, we're going to copy this, go into Delete Bookmark, paste this in, and update our method call to call Delete Bookmark.

[03:33] Everything is in place for our CRUD functionality within our application except for in the Bookmarks Reducer. Let's go ahead and start with the Create Bookmark handler. What we're going to do here is simply push the payload into state. This is one way to get an object into an array. It's simply using the .push method.

[04:01] You can see here that it does indeed work, but we are obviously mutating the state property. We're going to go object.freeze, and we're going to freeze the state object. Let's refresh and see what happens when we try this one more time.

[04:22] We'll give it a title, URL, Save, and object.freeze is now throwing an error. We need to find a way to do this without mutating the existing state object. The way to do that, one way, is using the concat method on array.

[04:40] We can go state.concat, and then pass in the payload. This returns a new array with the payload at the end of it. Let's go ahead and try this out again. You can see that it added it to the collection, and we did not trigger any error from object.freeze.

[04:59] There's a shorthand way to do this, by essentially creating a new array and then using the spread operator to pass in state and payload. We'll just verify that this is working as well, and we're not throwing any errors. We're go to go.

[05:16] Avoid using array.push. Instead use array.concatonate or concat, and you can even use the shorthand method. Let's go ahead and do Update Bookmark. The way to do this in a mutable fashion is to find out the index in the collection of the object you want to update, and once you know the index, then you can reference that and pass in the new object.

[05:44] We're going to call state.find.index, and we're going to loop through and compare the IDs until we find the index that the payload is at. From there, we're just going to replace that item with the new payload.

[06:08] Let's see if this works. Refresh the page. We'll go ahead and save, and it does work, but I have a suspicion that this is a mutable operation. Let's throw a bunch of cold water on this parade. We'll do object.freeze.

[06:28] Let's try this again. We'll update this title, and you can see that it is a mutable operation, and so we need to find a way around this. What we need is essentially a new array that has the information that we need.

[06:50] We can use the map operator to accomplish this. By calling map, it returns a new collection that has been iterated over, and the operations performed that we define in our map method. In this case, we're saying, "Compare the bookmark ID to the payload ID. If they match, return the payload. If not, return the bookmark." We can delete these lines, and let's see if this works.

[07:24] It does, and we're not throwing in an error. The main reason why -- and focus on this -- is that map is returning a new collection and not mutating the old one.

[07:36] Let's do Delete Bookmark. We can do something similar above, is we can grab the index of the bookmark that we want to delete. We'll just do state.find Index, and we'll loop through and compare the IDs of the current item to the payload.

[07:59] Once we have the index, then we can call state.splice, pass in the index, and we're going to remove one item. Let's test this out.

[08:14] We are deleting bookmarks, but this is a mutable operation. Let's move object.freeze into this block and verify our assumptions. Yes, you can see that the minute we tried to delete something, because it's frozen, we are throwing an error.

[08:42] Just like state.map, state.filter returns a new collection as well, and so we can just filter out the bookmark that we want to delete and return that as a new collection. Let's go here, let's refresh, and let's delete all the bookmarks.

[09:02] We're able to get a new collection, minus the bookmark we want to delete, and we're not messing up object.freeze.

[09:12] Just to review, we hooked up our Save Bookmark, and Delete Bookmark method so that we could call that from the controller, and then we created a mutable Create Bookmark operation using the concat method.

[09:28] For Update Bookmark, we used state.map, which returns a new collection, and for Delete Bookmark, we used state.filter.

[09:38] This is how you do immutable operations within a Redux reducer in an Angular application.