Redux: Normalizing API Responses with normalizr

Share this video with your friends

Send Tweet

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

Andre Zimpel
Andre Zimpel
~ 7 years ago

Would be very cool if you could show how to manage relational data with normalisr and redux! :-)

Andre Zimpel
Andre Zimpel
~ 7 years ago

Especially when dealing with an API and success and failure and stuff like that.

Enoh Barbu
Enoh Barbu
~ 7 years ago

For this kind of trivial "apps" is not recommended (in my opinion) to use normalizr because while you simplify the reducers, you'll end up "complexify" the action creators with schemas that you'll need to create.

Mickey
Mickey
~ 6 years ago

The byId Reducer goes from

const byId = (state = {}, action) => {  
switch (action.type) {
    case 'FETCH_TODOS_SUCCESS': // eslint-disable-line no-case-declarations
      const nextState = { ...state };
      action.response.forEach(todo => {
        nextState[todo.id] = todo;
      });
      return nextState;
    case 'ADD_TODO_SUCCESS':
      return {
        ...state,
        [action.response.id]: action.response,
      };
    default:
      return state;
  }
};

to

const byId = (state = {}, action) => {
  if (action.response) {
    return {
      ...state,
      ...action.response.entities.todos,
    };
  }
  return state;
};

However my understanding is that ALL the actions are taken thru ALL the reducers.

So by removing the check for Action Type of 'FETCH_TODOS_SUCCESS' and 'ADD_TODO_SUCCESS' and simply checking if the action contains a response field, then I'm thinking, that what if our system has some other unrelated action that also returns a promise with a response field, and so in that scenario, our byId Reducer would also attempt to handle this unrelated action, which would be a bug.

Am I correct here, or have I missed something?

Oleksandr Marchenko
Oleksandr Marchenko
~ 5 years ago

For the modern version of normalizr (3.4.0 atm):

import { schema } from 'normalizr';

export const todo = new schema.Entity('todos');
export const arrayOfTodos = new schema.Array(todo);
J. Matthew
J. Matthew
~ 5 years ago

[...] what if our system has some other unrelated action that also returns a promise with a response field, and so in that scenario, our byId Reducer would also attempt to handle this unrelated action, which would be a bug.

@Mickey, you're right. Another action could easily have a response property by coincidence or oversight and thus get processed by byId unintentionally. This implementation is relying on us to remember that any property by that name will be picked up by that reducer. It might make more sense if the property name were specific to "todos," but instead it's the generic response. It's easy to imagine us expanding this app to fetch additional types of data and dispatching non-todo actions that also have a response property.

The result would most likely be a bug, as you said. The current code spreads action.response.entities.todos into state for todos, so it's expecting any response property to be an object with an entities property, which itself has a todos property. If we were using normalizr for this hypothetical other fetched data, it would probably include that same entities property, but I assume it would have some other dictionary name inside instead of todos. In that case, the value being spread into state would be undefined, which when spread has no effect, so that would probably be OK, albeit by luck. If we weren't using normalizr for that other fetch though, then most likely entities would be undefined, and then when the code tried to read todos off it, the app would throw an error.

I understand the desire to simplify the reducer code, but this seems like going too far, in a way that is antithetical to Redux itself. We're relying on normalizr for consistency, not Redux, and while we are still using actions, we're ignoring their types, which I thought was key to the tool's power. We're giving up precision control in favor of trusting that a certain arbitrary structure always means the same thing. I don't buy it.

On the whole, I find this particular lesson to be a bit strange. I'm not sure why it's here, in this otherwise-excellent course. I'm sure normalizr is a good tool, and I can imagine how it could be incredibly powerful in a very controlled environment. Maybe this lesson just isn't substantial enough to do that justice. Certainly the functionality of normalizr itself still seems a bit like magic to me; this is no deep dive.

I agree with @Enoh's point that the complexity normalizr introduces is overkill for this app. At the same time, I understand that this course is walking a fine line, trying to demonstrate sophisticated practices and concepts without taking the substantial additional time needed to build an app that actually needs such sophistication. Most of the time it doesn't bother me, other than occasionally confusing me, but I think we could have skipped this particular topic.