Search code examples
reduxreact-reduxredux-toolkitcreateentityadapter

Nested Data Normalization createEntityAdapter


I am trying to normalize an array of data of the following structure Array<Application>. using the createEntityAdapter.

After fetching the data with a createAsyncThunk and returning them, I did set them in the extraReducers like this:

applicationsAdapter.setAll(state, action.payload)

In the Redux DevTools I could see that the data look like this:

{
    applications: {
      ids: [
        '60a684fb90957536f185ea88',
      ],
      entities: {
        '60a684fb90957536f185ea88': {
          id: '60a684fb90957536f185ea88',
          title: 'Article1',
          description: 'The best article ever',
          groups: [
            {
              id: 'bPDGd8uOkmKKAO3FyoTKE',
              title: 'Group 1',
              voters: [
                {
                  user: {
                    uid: '344bc9b8-671b-4de5-ab2d-a619ea34d0ba',
                    username: 'tran',
                  },
                  vote: 'DECLINED'
                }
              ]
            }
          ],
          deadline: "2021-05-20",
          budget: 0,
          createdAt: '2021-05-20',
          createdBy: {
            uid: 'ab2a8f19-c851-4a5f-9438-1000bfde205a',
            username: 'admin',
          },
     },
}

QUESTION: The data is not fully normalized. How can I normalize the groups, voters and users objects?

Here are the interfaces:

export interface Application {
    id: string;
    title: string;
    description: string;
    groups: Array<GroupElement>;
    deadline: string;
    budget?: number;
    createdAt: string;
    createdBy: User;
}


export interface GroupElement {
    id: string;
    title: string;
    voters: Array<Voter>;
}

export interface User {
    uid: string;
    username: string;
}

export interface Voter {
    user: User;
    vote: Decision;
}

Solution

  • createEntityAdapter does not perform any sort of relationship normalizing on its own. You need to use normalizr for that.

    This is a tough one to normalize due to the Voter object which lacks an id property of its own. I had to piece one together.

    import { schema, normalize } from "normalizr";
    
    const user = new schema.Entity("user", {}, { idAttribute: "uid" });
    
    const voter = new schema.Entity(
      "voter",
      {
        user: user
      },
      {
        idAttribute: (object) => `${object.vote}-${object.user.uid}`
      }
    );
    
    const group = new schema.Entity("group", {
      voters: [voter]
    });
    
    const application = new schema.Entity("application", {
      createdBy: user,
      groups: [group]
    });
    
    normalize(entity, application);
    

    That will transform your data into:

    {
      "entities": {
        "user": {
          "ab2a8f19-c851-4a5f-9438-1000bfde205a": {
            "uid": "ab2a8f19-c851-4a5f-9438-1000bfde205a",
            "username": "admin"
          },
          "344bc9b8-671b-4de5-ab2d-a619ea34d0ba": {
            "uid": "344bc9b8-671b-4de5-ab2d-a619ea34d0ba",
            "username": "tran"
          }
        },
        "voter": {
          "DECLINED-344bc9b8-671b-4de5-ab2d-a619ea34d0ba": {
            "user": "344bc9b8-671b-4de5-ab2d-a619ea34d0ba",
            "vote": "DECLINED"
          }
        },
        "group": {
          "bPDGd8uOkmKKAO3FyoTKE": {
            "id": "bPDGd8uOkmKKAO3FyoTKE",
            "title": "Group 1",
            "voters": ["DECLINED-344bc9b8-671b-4de5-ab2d-a619ea34d0ba"]
          }
        },
        "application": {
          "60a684fb90957536f185ea88": {
            "id": "60a684fb90957536f185ea88",
            "title": "Article1",
            "description": "The best article ever",
            "groups": ["bPDGd8uOkmKKAO3FyoTKE"],
            "deadline": "2021-05-20",
            "budget": 0,
            "createdAt": "2021-05-20",
            "createdBy": "ab2a8f19-c851-4a5f-9438-1000bfde205a"
          }
        }
      },
      "result": "60a684fb90957536f185ea88"
    }