Redux does not work well with non-JSON data formats. So you’ve got strings, booleans, numbers and arrays and basic objects. That’s pretty much what you get to work with in your redux store. No sets. No maps. No classes or functions. Just the most basic JavaScript constructs going back for years.
However, even with these constraints it’s still pretty easy to reach for the wrong data structure in redux. The main culprit that I’ve run into is when working with lists of objects, let show you to use them correctly.
I demonstrate 3 concepts:
Object.values
This is a rather long lesson, but I hope you find it helpful. In my experience using objects for lists in redux is preferred over arrays. This helps avoid the issues of accidentally modifying an objects siblings in a reducer (trigger extra re-renders) AND simplifies grabbing a specific object in a selector.
The immer library is now part of the redux toolkit and frequently recommended as a way to simplify updating objects in your reducers. We do not cover its use in this course.
Jamund Ferguson: [0:00] Let's start by reformatting our currencyData into an array of objects. This might seem pretty normal at first.
[0:06] For this, we'll have USD as code property and 1. as the rate property. For currency data, we'll have an object with a key of code with a value of USD and a key of rate and a value of 1..
[0:24] We'll now go into the ratesReceived case in our reducer and make sure that our currency data is formatted properly. Here, we can say const currencyData = empty array. We'll use that in our return object. Then, for (let code in action.payload).
[0:46] Remember, our server didn't change the way that it was returning the data. We're changing the way that we're storing the data, so we need to do that here. currencyData.push will push in the code, and for the rate, action.payload(code). Now, we should have currencyData in the correct format.
[1:05] Let's go into RateTable, and instead of Object.entries(currencyData) is already an array, so we can simply map over it directly. Instead of using array destructuring, we can use object destructuring to grab the code and write properties of each of those currencyData objects. Let's refresh the page. As far as I can tell, everything's working completely fine. Everything's loading, as expected.
[1:27] Let's consider if we wanted to add maybe an additional property to our currencyData. For example, something like displayLabel. Here, it might be "US Dollars" for USD. To populate this displayLabel, let's assume we have to make a separate API call, and instead of them all coming in at once, they might have to come in one by one.
[1:45] We can create a new case called rates/labelReceived, and we'll block that out again with curly braces. We'll grab the displayLabel and the currencyCode that it's associated with. Obviously, at some point, we need to return the state. How do I update this specific member of this array?
[2:06] First of all, we need to create a copy of the state like we've been doing everywhere else. For the currencyData property, we need to map over the existing state. We'll type currencyData: state.currencyData.map.
[2:21] Then for each item in the currencyData, if (currencyCode ===...This is the currencyCode being passed in by the action, basically saying, I have a displayLabel for euros or a displayLabel for Japanese yen.
[2:36] If the currencyCode matches the currencyCode of the specific item that I'm looking for, it will return ...data.
[2:43] We're copying the existing data over or updating the displayLabel property by passing in the one that was sent in with our action. We're going to map through the entire array, and if we found the one that matches, we're going to create a copy of it. Otherwise, return data.
[3:00] It's super important that we don't create a copy or modify in any way any of the items in the array that have not changed. You only want to create new items where the data has changed. If it thinks that your data changed, it's going to re-render a component. If we're careful not to update our data when it doesn't need to be changed, we'll be all right.
[3:22] Let me show you another case that you have to look out for when dealing with lists of objects, and that's selectors. Let's go ahead and create a new selector called export const getDisplayLabel. getDisplayLabel, unlike all of our previous selectors, is going to take state, as well as a currencyCode.
[3:39] With a currencyCode, type const match = state.rates.currencyData.find() and pass in a function data, data.code === currencyCode. Basically, we have to loop through all of the items in currencyData to find the specific one that matches. If we find that one, if (match), we can return match.displayLabel.
[4:06] When you're dealing with an array of values, when you have a selector, you have to loop through every single item in the array to find the one that you want.
[4:14] If it seems likely that you're going to need access to individual items in your array, for example, our currencyData, if I'm going to need access to updating currencyData within these objects or accessing these, it might make more sense to store your data in an object with a key.
[4:33] For our data structure here, replace the array with an outer object. We're going to give this inner object a key of USD. It will be the same as its currency code.
[4:44] We can then go down here and instead of having to worry about mapping over all of the existing data, we will say currencyData, curly brace, and then inside we're going to copy the existing currencyData back over. Then, we use this unique syntax here for variable key names and objects.
[5:03] We can say currencyCode, because that's the key. Let's copy everything over exactly from currencyData, but let's override just one particular child, and that's the one with the key of currencyCode. For that particular one, we'll say copy over everything that was there before, so currencyData, currencyCode, and then update the displayLabel.
[5:27] It's a little bit more tedious, I'll admit, to update an object, but you get a nice benefit when it comes down to the selector.
[5:34] In the selector before, we had to call state.rates.currencyData.find, or you can potentially do a for loop over everything. With this approach, we can simply say const match = state.rates.currencyData [currencyCode] . It doesn't require looping over everything. We can immediately access the data that we want.
[5:51] Here, in order to create this object, we simply make that an object and do currencyData [code] equals that. Finally, in RateTable, we can say Object.values to create an array out of all the values from currencyData object.
[6:12] With that in place, it works again. Our selector is much simpler than before. We don't have to worry about the issue of recreating every single item in the array because we're making sure to use our key to only change the one specific element that we need to change.