1

So, I have a response that gets paginated users, and it looks something like this ...

{
  data:[{...}, {...}],
  links: {first:..., last:...},
  meta: {current_page: 1, total: 400, from: 1, to: 10}
}

Now, when using createEntityAdapter to normalize the data, I have to pass just the "data" array of objects to it, not the whole response responseData.data, otherwise it won't work ...

    const usersAdapter: any = createEntityAdapter({});
    const initialState = usersAdapter.getInitialState();
    
    export const usersApiSlice = apiSlice.injectEndpoints({
      endpoints: builder => ({
        getUsers: builder.query<MetadataObj, MetadataObj>({
          query: options => ({
            url: "/users",
            params: options,
          }),
          transformResponse: (responseData: MetadataObj) => {
            return usersAdapter.setAll(initialState, responseData.data);
          },
          providesTags: (result, error, arg) => {
            if (result?.ids) {
              return [
                { type: "User", id: "LIST" },
                ...result.ids.map((id: Number) => ({ type: "User", id })),
              ];
            } else return [{ type: "User", id: "LIST" }];
          },
        }),
      }),
    });
    
    export const { useGetUsersQuery } = usersApiSlice;
    
    export const selectUsersResult = usersApiSlice.endpoints.getUsers.select({ page: 1, per_page: 10 });
    const selectUsersData = createSelector(selectUsersResult, usersResult => usersResult.data);
    
    export const {
      selectAll: selectAllUsers,
      selectById: selectUserById,
      selectIds: selectUserIds,
    } = usersAdapter.getSelectors((state: RootState) => selectUsersData(state) ?? initialState);

Now, how do I get the "meta" and "links" keys as well? I tried to search the docs, but they always assumed the response to be an array of objects.

What I did for now is that I created a "usersSlice" beside the "usersApiSlice", and I stored the metadata inside of it like this ...

import { createSlice } from '@reduxjs/toolkit';

import { MetadataObj } from '../../../types/globalTypes';
import { RootState } from '../../app/store';

type stateType = {
  requestMeta: MetadataObj;
};

const initialState: stateType = {
  requestMeta: {},
};

const usersSlice = createSlice({
  name: "user",
  initialState,
  reducers: {
    setRequestMeta: (state, action) => {
      state.requestMeta = action.payload;
    },
  },
});

export const { setRequestMeta } = usersSlice.actions;
export const selectRequestMeta = (state: RootState) => state.user.requestMeta;
export default usersSlice.reducer;

And then I use the transformResponse function after the query function to catch the meta from the original response and store it in the usersSlice

transformResponse: (responseData: MetadataObj, meta, arg) => {
  store.dispatch(setRequestMeta(responseData.meta));
   return usersAdapter.setAll(initialState, responseData.data);
}

However, I have a feeling that there should be a better way to handle this. I'm not sure, but there should be.

Any help is apprecitated.

Ruby
  • 2,207
  • 12
  • 42
  • 71

1 Answers1

0

Here is a code snippet of how I got it to work for my use case. Try to understand how this code works and I'm sure you will solve your problem. Note, this is done with TypeScript.

import type { EntityId, Dictionary } from "@reduxjs/toolkit";
import { createSelector, createEntityAdapter } from "@reduxjs/toolkit";

import type { IInfo, IPaginatedGetPosts, IPostsForUser } from "../../types";
import { postsApi } from "../api/postsApi";

const postsAdapter = createEntityAdapter({
  selectId: post => post.postId,
  sortComparer: (a: IPostsForUser, b: IPostsForUser) => b.createdAt.localeCompare(a.createdAt),
});

const initialState = postsAdapter.getInitialState({
  info: {
    currentPage: 0,
    numberOfPosts: 0,
    pageSize: 0,
    pages: 0,
    next: null,
    previous: null,
  },
});

export const extendedApiSlice = postsApi.injectEndpoints({
  endpoints: builder => ({
    // we are getting a response of type IPaginatedGetPosts but we are transforming that response when normalizing state
    // so this is the new return type of the query
    getPosts: builder.query<
      {
        info: IInfo;
        ids: EntityId[];
        entities: Dictionary<IPostsForUser>;
      },
      void
    >({
      query: () => {
        return {
          // allow httpOnly cookies to be sent
          credentials: "include",
          method: "GET",
          url: "/get-posts",
        };
      },
      transformResponse: (rawResult: IPaginatedGetPosts) => {
        // set initial state to rawResult.postsForUser and also set info from initialState to rawResult.info
        return postsAdapter.setAll({ ...initialState, info: rawResult.info }, rawResult.postsForUser);
      },
      providesTags: result => {
        if (result) {
          // result is object of entities ids and info
          // entities is an object with keys of ids and values of posts
          // we need to turn it into an array of posts
          return [
            ...Object.values(result.entities).map(post => ({ type: "Post" as const, postId: post.postId, userId: post.User.userId })),
            { type: "Post", postId: "LIST", userId: "LIST" },
          ];
        }
        return [{ type: "Post", postId: "LIST", userId: "LIST" }];
      },
    }),
  }),
});

export const { useGetPostsQuery } = extendedApiSlice;
Elco
  • 97
  • 1
  • 7