Skip to content

feat: overwrite query key on trpc.useQuery options #4989

@zirkelc

Description

@zirkelc

Describe the feature you'd like to request

I have query procedure listPosts which accepts some generic parameters and and optional lastUpdatedAt timestamp.
The query key looks something like this:

[["posts","list"],{"input":{ "parameters": {} },"type":"query"}]

This query is expensive, so I cache it also on the server with the given parameters as caching key in case multiple clients request the same data. When I receive the same parameters on the server, I return the data from the cache.

Now I need some way for clients to force a refresh on the server returning new data instead of cached data for the same parameters. So in this case, I query the listPosts with an optional lastUpdatedAt timestamp. The query key is now:

[["posts","list"],{"input":{ "parameters": {}, "lastUpdatedAt": 123456789 },"type":"query"}]

The servers receives the same parameters as before and finds the data in the cache, but the lastUpdatedAt timestamp is greater than the cached timestamp. So it runs the expensive operation on the server and returns the new data (and caches it).

Now I have two query keys on the client which are logically the same. I must update the query data of key 1 and key 2 with new data, because query key 2 is only temporary to trigger a server cache-invalidation. The lastUpdatedAt that I sent in query key 2 is a local state that will get lost after page navigation. When I later get back to the ListPosts page, it will use query key 1 again with data which was maybe set by query key 2.

I'm currently using onSuccess of useQuery to set the query data of all query keys matching the input without lastUpdatedAt:

type Input = {
  parameters: Record<string, any>;
  lastUpdatedAt?: number;
};

const query = trpc.posts.list.useQuery(input, {
  onSuccess(data) {
    const { lastUpdatedAt, ...restInput } = input;
    // update the query cache for all entries with the same parameters, but ignoring the lastUpdatedAt date
    queryClient.setQueriesData(getQueryKey(trpc.posts.list, restInput), data);
  },
});

That means it sets the data for query key 1 (without lastUpdatedAt) and 2 (with lastUpdatedAt).


However, onSuccess was deprecated in React Query v5, so I'm looking for an alternative option.
Allowing to overwrite the generated tRPC query key would make it possible to send different inputs to the server but with the same query key.

Describe the solution you'd like to see

I would like to overwrite the query key via the tRPC query options:

const query = trpc.posts.list.useQuery(input, {
  trpc: {
    // static query key overwrite
    queryKey: [["posts","list"],{"input":{ "parameters": {} },"type":"query"}],
    // OR
    // dynamic query key overwrite
    queryKey: () => {
      // remove field that should not be passed to server
      const { lastUpdatedAt, ...restInput } = input;
      return getQueryKey(trpc.posts.list, restInput)
    },
  },
});

That means the following two inputs produce the same query key:

// = query key: [["posts","list"],{"input":{ "parameters": {} },"type":"query"}]
const input1 = {
  parameters: {},
}

// = query key: [["posts","list"],{"input":{ "parameters": {} },"type":"query"}]
const input2 = {
  parameters: {},
  lastUpdatedAt: 123456789
}

By default, React Query would return the data from the query cache for both inputs. So we would need to use the refetch() function to manually trigger a refetch to the server with new input.

Describe alternate solutions

  1. useQuery.onSuccess() was deprecated in v5, but could be implemented again with a useEffect() with dependency to useQuery.data
  const { data } = trpc.posts.list.useQuery(input);

  // update all queries every time the data changes
  React.useEffect(() => {
    const { lastUpdatedAt, ...restInput } = input;
    queryClient.setQueriesData(getQueryKey(trpc.posts.list, restInput), data);
  }, [data, queryClient, input]);
  1. use useQuery directly with the proxy client and change the query key
const { lastUpdatedAt, ...restInput } = input;
const queryKey = getQueryKey(trpc.posts.list, restInput);

const query = useQuery({
  queryKey,
  queryFn: (context) => {
    return utils.client.posts.list.query(input)
  },
});

Additional information

No response

👨‍👧‍👦 Contributing

  • 🙋‍♂️ Yes, I'd be down to file a PR implementing this feature!

Metadata

Metadata

Assignees

No one assigned

    Labels

    ✅ accepted-PRs-welcomeFeature proposal is accepted and ready to work on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions