Redux: Normalizing API Responses with normalizr

    Dan AbramovDan Abramov

    We will learn how to use normalizr to convert all API responses to a normalized format so that we can simplify the reducers.



    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson


    00:00 In the by ID reducer, I currently have to handle different server actions in a different way, because they have different response shape. For example, the fetch to-do success action has a response, which is an array of to-dos.

    00:14 I have to iterate over them and merge them one by one into the next state. Add to-do success is different. The response for adding a to-do is the to-do itself. I have to merge a single to-do in a different way.

    00:28 Instead of adding new cases for every new API call, I want to normalize the responses so the response shape is always the same.

    00:37 I'm running NPM install, save Normalizer, which is a utility library that helps me normalize API responses to have the same shape. I'm creating a new file in the actions directory called "schema.js." I'm importing a schema constructor, and a function called array of from Normalizer.

    00:58 I will export two schemas from this file. I create a schema for the to-do objects, and specify to-dos as the name of the dictionary in the normalized response. I also create another schema called array of to-dos that corresponds to the responses that contain arrays of to-do objects.

    01:22 Next, I am opening the file where I define action creators, and I am adding a named import for a function called "normalize" that I import from Normalizer. I also add a namespace import for all the schemas I defined in the schema file.

    01:38 I'm scrolling down to the fetch to-do success callback, and adding a normalized response log so that I can see what the normalized response looks like. I'm calling the normalize function with the original response as the first argument, and the corresponding schema -- in this case, array of to-dos -- as the second argument.

    02:02 Next, I'm scrolling down to the add to-do success handler. When the response comes back, I want to log the normalized response by calling the normalize function with the original response as the first argument, and the corresponding schema -- in this case, a schema for a single to-do -- as the second argument.

    02:24 If I run the app now and look at the response in the action, I will see an array of to-do objects, however, a normalized response for fetch to-do success action looks differently. It contains two fields called entities and result.

    02:43 Entities contains a normalized dictionary called "to-dos" that contains every to-do in the response by its ID. Normalizer found these to-do objects in the response by following the array of to-dos schema. Conveniently, they are indexed by IDs, so they will be easy to merge into the lookup table.

    03:07 The second field is the result. It's an array of to-do IDs. They are in the same order as the to-dos in the original response array. However, Normalizer replaced each to-do with its ID, and moved every to-do into the to-dos dictionary.

    03:24 Normalizer can do this for any API response shape. For example, let's add a to-do. The original action response object will be the to-do itself, as returned by the server. The normalized response will contain two fields, just like before, entities and result.

    03:49 Like before, the entities object will contain the to-dos dictionary, this time with a single item. In the result field, we will see just the ID of the to-do, because the original response is just the single to-do, and Normalizer replaced it with its ID in the result field.

    04:10 I will now change the action creator so that they pass the normalized response in the response field, instead of the original response.

    04:19 As a reminder, we have to pass the schema as the second argument, and its schema to-do for the single to-do, and its schema array of to-dos for the array of to-do objects in the response.

    04:33 Now, I can open the by ID reducer, and I can delete these special cases, because the response shape is going to be very similar. Rather than switch by action type, I will check if the action has a response object on it.

    04:49 I will return a new version of the lookup table that contains all existing entries, as well as any entries inside entities to-dos in the normalized response. For other actions, I will return the lookup table as it is.

    05:05 Now, I need to switch to the IDs reducer to amend it to understand the new action response shape. For fetched to-dos, it used to be an array of to-dos. For add to-do, it used to be the to-do itself.

    05:20 Now, the action response has a result filled, which is already an array of IDs, in case of fetch this success, and our single ID of the fetched to-do in case of add to-do success.

    05:33 I can run the app now, and inspect the action response. I can see that, for fetched to-do success, the response contains the entities which contains the to-dos by their IDs, and the result is an array of IDs in the same order as they were in the original response.

    05:51 I can also add a to-do, and the action response will also contain the entities and the result, where the entities contains the to-dos by their IDs, in this case, a single to-do. The result is the ID of the added to-do.

    06:08 Let's recap how to work with normalized responses. Fetch to-do success original response contained an array of to-dos. Normalizer replaces them with an array of their IDs in the result field. The add to-do success original response was a single to-do, so action response result becomes its ID.

    06:30 Inside the by ID reducer, I removed all the special cases for different action types. I just check if the action contains a normalized response. The entities field will contain different dictionaries. In this example, I only have a single to-dos dictionary, which corresponds to the objects with a to-do schema.

    06:54 I use the object spread operator to merge the old lookup table and the newly-fetched to-dos. The name of the dictionary inside entities corresponds to the string argument that I passed to the schema constructor when I created the to-do schema.

    07:12 I have two kinds of API responses in my app, a single to-do, and an array of to-dos. I use the array all function from Normalizer to create a corresponding array schema.

    07:25 Finally, in the action creators, I call the normalize function to get the normalized response from the original response, and the schema that I know corresponds to this API endpoint.

    07:38 I know that the original response from fetch to-dos is an array of to-do objects. I pass the array of to-dos schema. The add to-do response shape is a single to-do item. I pass the schema for a single to-do to the normalize function that I import from Normalizer.