Skip to content

Allow dynamically adding more values to queryKey array to allow for better partial invalidation #72

@prabhg

Description

@prabhg

Here is a contrived example:

type Response = {
   title: string; 
   content: string;
   user: User | null;
   comments: Comments[] | null;
   likesCount: number | null;
}

type Variables = { 
  id: string;
  includeUserData?: boolean;
  includeComment?: boolean;
  includeLikes?: boolean;
}

const usePost = createQuery({
  queryKey: ['posts'],
  fetcher: (variables: Variables): Promise<Response> => {
    return graphqlRequest({
        variables,
        authToken
    })
  }
})

Here usePost can be invoked with many different combination of variables:

usePost({ variables: { id: postId, includeUserData: true } })
usePost({ variables: { id: postId, includeComment: true, includeUserData: false } })
usePost({ variables: { id: postId, includeComment: true, includeLikes: true } })

Issue:

In this example, same post data gets cached with different combination of variables. Problem is that when I want to invalidate this post's data, I would have to manually track all possible variable combination in use in the code and invalidate them. I can do queryClient.invalidateQueries({ queryKey: ['posts'] }) but that would invalidate data for all the posts.

This issue can be avoided when directly using useQuery(..) by storing the postId as one of the element in queryKey array to allow for partial query matching during invalidation (https://tanstack.com/query/v5/docs/framework/react/guides/query-invalidation#query-matching-with-invalidatequeries).

const postResponse = useQuery({
  queryKey: ['posts', { postId: 123 }], // OR ['posts', 123]
  queryFn: fetchPost,
})

// invalidate using partial query matching
queryClient.invalidateQueries({ queryKey: [ 'posts', { postId: 123 } ] });

I tried researching but couldn't find if there is an existing solution to achieve something similar with react-query-kit.

Proposed Solution:

If there was a way to dynamically add more values to the queryKey array between the provided keys when createQuery() is called and the variables that get appended to end of that array, we can benefit from partial query matching.

One possible solution

Add support for a new optional "selector" function that returns another array of values that would be inserted between values provided in queryKey and variables. This method should get called from getKey() as well to keep cacheKey creation deterministic.

I havent looked through entire source code, but it might be as simple as making this change:

const getKey = (
  queryKey: QueryKey, 
  queryKeyAppend: (variables?: any) => any[] | null | undefined = () => null, 
  variables?: any): QueryKey => {
      const newKeys = queryKeyAppend(variables);
      if (newKeys) {
           queryKey = [...queryKey, ...newKeys];
      }
      return variables === undefined ? queryKey : [...queryKey, variables]
}

Usage Example

const usePost = createQuery({
  queryKey: ['posts'],

  // (optional) returned value from this, if not null/undefined, should be appended between "queryKey" value above and "variables"
  queryKeyAppend: (variables: Variables): any[] => {
    return [{ postId: variables.id }] // this should cause final cache key to be ['posts',  { postId: variables.id }, ...variables ]
  },
  
fetcher: (variables: Variables) => { return ... },
})

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions