Search code examples
reactjstypescriptredux-toolkitrtk-query

I render two different components at same time, with the same Redux-Toolkit Query, but using different params


I have a component that is a carousel that fetches 8 items using Redux-Toolkit Query (RTKQ), and another component on the sidebar, that fetches 3 items as well using RTKQ. They both render at same time, and they use the same RTKQ, sometimes, there is a bug that makes them both share the same amount of items (while they should not)

They both use this query.

const { data } = useGetPopularClipsQuery({ limit: 3, page: 1 });
const { data } = useGetPopularClipsQuery({ limit: 8, page: 0 });

RTKQ

getPopularClips: builder.query<
  { posts: Array<Post>; hasMore: boolean },
  { limit?: number; page: number }
>({
  providesTags: ['PopularClips'],
  query: ({ limit = 5, page }) => ({
    url: `/post/popular-clips`,
    params: {
      limit,
      page
    }
  }),
  transformResponse(baseQueryReturnValue: Post[], meta, arg) {
    return {
      posts: baseQueryReturnValue.map((clip) => ({
        ...clip,
        ...arg,
        fromQuery: `getPopularClips`
      })),
      hasMore: baseQueryReturnValue.length > 0havr 
    };
  },
  serializeQueryArgs: ({ endpointName }) => endpointName,
  merge: (currentCache, newItems) => ({
    posts: uniqBy(
      [...currentCache.posts, ...newItems.posts],
      (clip) => clip.id
    ),
    hasMore: newItems.posts.length > 0
  }),
  forceRefetch: ({ currentArg, previousArg }) =>
    currentArg?.page !== previousArg?.page
}),

Both components, must render at same time, but they should have a different amount of items.


Solution

  • You use the same query cache key for all subscriptions, the endpoint name.

    serializeQueryArgs: ({ endpointName }) => endpointName,
    

    Typically you'd not specify serializeQueryArgs and let the defaultSerializeQueryArgs function handle it, which creates a cache key that is a combination of the endpoint name and a stringified version of the query args.

    Removing serializeQueryArgs from the getPopularClips endpoint would allow cache entries to be created using the page and limit parameters. If both (or any duplicate) parameter sets are used across two or more hook calls then they would still share a cache entry since the cache key would resolve to be the same.

    If for some reason you still wanted separate query hooks with duplicate query args to be cached separately, then I'd suggest adding argument another parameter to disambiguate the query call.

    Example:

    getPopularClips: builder.query<
      { posts: Array<Post>; hasMore: boolean },
      { id?: string; limit?: number; page: number }
    >({
      providesTags: ['PopularClips'],
      query: ({ limit = 5, page }) => ({
        url: `/post/popular-clips`,
        params: {
          limit,
          page
        }
      }),
      transformResponse(baseQueryReturnValue: Post[], meta, arg) {
        return {
          posts: baseQueryReturnValue.map((clip) => ({
            ...clip,
            ...arg,
            fromQuery: `getPopularClips`
          })),
          hasMore: baseQueryReturnValue.length > 0havr 
        };
      },
      merge: (currentCache, newItems) => ({
        posts: uniqBy(
          [...currentCache.posts, ...newItems.posts],
          (clip) => clip.id
        ),
        hasMore: newItems.posts.length > 0
      }),
      forceRefetch: ({ currentArg, previousArg }) =>
        currentArg?.page !== previousArg?.page
    }),
    
    const { data } = useGetPopularClipsQuery({ id: "hook1", limit: 3, page: 1 });
    
    const { data } = useGetPopularClipsQuery({ id: "hook2", limit: 3, page: 1 });