skip to Main Content

I am using react/redux and here are the user endpoints, as you can see I have a getUsers and also getSingleUser. in my app, when I first go to the usersList page which uses the getUsers Query, all the users are fetched and provided a tag, but when I click on each individual user to go to the singleUser page which uses getSingleUser, I can see that redux is not reading the user data from the previusly cached tags and it’s re-fetching the user again, and providing it another tag, so what would happen is that now I have to tags for the same user (65e0ef744995aa9b6b8d1af4[0] and 65e0ef744995aa9b6b8d1af4[1]), one for the user that was fetched with getUsers and one for the one that was fetched with getSingleUser.

how can I fix this issue?

import { apiSlice } from "../apiSlice";

const userApiSlice = apiSlice.injectEndpoints({
  endpoints: (builder) => ({
    getUsers: builder.query ({
      query: () => 'user',
      providesTags: (result, error, context) => {
        if (!error && result) {
          return [{type: 'User', id: 'LIST'}, ...result.users.map(user => ({type: 'User', id: user._id}))]
        } else {
          return [{type: 'User', id: 'LIST'}]
        }
      }
    }),
    getSingleUser: builder.query ({
      query: (id) => `user/${id}`,
      providesTags: (result, error, context) => {
        if (result && !error) {
          return [{type: 'User', id: result.user._id}]
        }
      }
    }),
    getUserBySearch: builder.query ({
      query: (search = '') => `user/search?q=${search}`,
      providesTags: (result, error, context) => {
        if (!error && result) {
          return [{type: 'User', id: 'LIST'}, ...result.users.map(user => ({type: 'User', id: user._id}))]
        } else {
          return [{type: 'User', id: 'LIST'}]
        }
      },
      keepUnusedDataFor: 5,
    }),
    createUser: builder.mutation({
      query: (credentials) => ({
        url: 'user',
        method: 'POST',
        body: credentials,
      }),
      invalidatesTags: [{type: 'User', id: 'LIST'}]
    }),
    deleteUser: builder.mutation ({
      query: (id) => ({
        url: `user/${id}`,
        method: 'DELETE'
      }),
      invalidatesTags: (result, error, context) => {
        console.log(`this is result: ${JSON.stringify(result)}`)
        if (result?.id && !error) {
          return [{type: 'User', id: result.id}]
        }
      }
    })
  })
})

export const {
  useGetUsersQuery,
  useGetSingleUserQuery,
  useGetUserBySearchQuery,
  useCreateUserMutation,
  useDeleteUserMutation
} = userApiSlice;

I excpect to after using getUsers Query, to get SingleUser data immediately without having to wait.
btw, I am using redux devtools, that’s how I know two intances of the same tag is being made

2

Answers


  1. The getSingleUser isn’t going to be able to get the values from a different endpoint’s cache. Instead, whenever you change something that invalidates the tag for a user, it will cause a refetch of both the query that fetches the collection and the individual query for that one tag.

    Probably a better way to handle this is to provide the users you already got into something that displays via dependency injection (component props), and then if you need it somewhere you won’t have all the users, use your getSingleUser hook in a level above the View that displays data for just that user. If you build your View just to display the user data and not care where that comes from, you can handle getting that data in the parent component. Separation of concerns FTW!

    Login or Signup to reply.
  2. Each endpoint is effectively its own entity, its own cache is used. Endpoints are not aware of the cached results of other endpoints. The behavior you describe sounds like applied optimistic/pessimistic updates. The getUsers endpoint can update the cache of other endpoints upon success.

    See Manual Cache Updates, specifically Creating new cache entries or replacing existing ones for details.

    Use the getUser endpoint’s onQueryStarted method to update the getSingleUser endpoint’s cache, effectively pre-populating that endpoint with user data by query id argument.

    Example:

    import { apiSlice } from "../apiSlice";
    
    const userApiSlice = apiSlice.injectEndpoints({
      endpoints: (builder) => ({
        getUsers: builder.query ({
          query: () => 'user',
          providesTags: (result, error, context) => {
            if (!error && result) {
              return [
                { type: 'User', id: 'LIST' },
                ...result.users.map(
                  user => ({ type: 'User', id: user._id })
                )
              ];
            } else {
              return [{ type: 'User', id: 'LIST' }];
            }
          },
          onQueryStarted: async (_, { dispatch, queryFulfilled }) => {
            try {
              const { data } = await queryFulfilled;
    
              await Promise.all(
                data.map(user => dispatch(
                  api.util.upsertQueryData(
                    'getSingleUser',             // endpoint cache to create/update
                    user._id,                    // user id query arg
                    (draft) => {
                      Object.assign(draft, user) // user data to cache
                    }
                  )
                )).unwrap()
              );
            } catch(error) {
              // handle/ignore
            }
          },
        }),
        getSingleUser: builder.query ({
          query: (id) => `user/${id}`,
          providesTags: (result, error, context) => {
            if (result && !error) {
              return [{ type: 'User', id: result.user._id }]
            }
          }
        }),
        ...
      })
    });
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search