connectrpc / connect-query-es

TypeScript-first expansion pack for TanStack Query that gives you Protobuf superpowers.
https://connectrpc.com/docs/web/query/getting-started
Apache License 2.0
218 stars 14 forks source link

Add ability to easily update query cache #390

Open nickzelei opened 1 month ago

nickzelei commented 1 month ago

Hey all, awesome project! This is allowing me to easily switch away from swr over to using tanstack/query.

One thing I miss however is the ability to easily update the query cache.

With swr, each instance has a mutate function that includes the curried key, so I can easily pass it the new value. With the current iteration of this library, the refetch function that is returned from useQuery takes no parameters.

In order for me to update the underlying cache, I have to do something like this:

const queryclient = useQueryClient();
const { mutateAsync } = useMutation(createMyValue);

async function onSubmit(values: MyFormValues): Promise<void> {
  const resp = await mutateAsync(...values...);
  queryclient.setQueryData(
    createConnectQueryKey(geyMyValue, {
      id: resp.myvalue.id,
    }),
    new GetMyValueResponse({
      myvalue: resp.myvalue,
    })
  );
}

I'd love it if there were an easier way to trigger a cache update for a useQuery value that didn't involve so much boilerplate.

timostamm commented 1 month ago

Thanks for the issue! In general, invalidating queries (triggering a refresh) instead of manipulating the cache is preferable because it's so simple and reliable (for example, you'll never forget to also update a lastUpdated property).

For example, I've often seen a pattern like this:

  const res = useMutation(createMyValue, {
    onSuccess: (data) =>
      queryClient.invalidateQueries({
        queryKey: createConnectQueryKey(geyMyValue, {
          id: data.id,
        }),
      })
  });

I'm not closely familiar with swr. Is it more common to update the cache than to let it update?

nickzelei commented 1 month ago

Thanks for the response! Unclear if it's more common or not to do that. swr works effectively the same way as the refetch does with their mutate, but they optionally allow you to provide a cache value.

Since our mutation APIs like Create, Update return the latest object, I like to optimistically update the cache because we have the latest object instead of just calling refetch again to hit the API one more time.

Perhaps this is an anti-patten and it's just way more common to simply call refetch or invalidate.

timostamm commented 1 month ago

Since our mutation APIs like Create, Update return the latest object, I like to optimistically update the cache

I'm not sure I'd call it an anti-pattern, just that I would generally recommend to invalidate and refetch for simplicity. Makes perfect sense to me to not waste a roundtrip, if you know what you're doing 🙂

Here's the tanstack/query guide on this topic: https://tanstack.com/query/latest/docs/framework/react/guides/updates-from-mutation-responses

Maybe the most straight-forward improvement would be a function similar to this:

import {type Message, type PartialMessage,} from "@bufbuild/protobuf";
import {QueryClient} from "@tanstack/react-query";
import {createConnectQueryKey, type MethodUnaryDescriptor} from "@connectrpc/connect-query";

export function setQueryData<
  I extends Message<I>,
  O extends Message<O>,
>(
  client: QueryClient,
  methodDescriptor: Pick<MethodUnaryDescriptor<I, O>, "I" | "name" | "service">,
  input: PartialMessage<I>,
  output: O,
): void {
  const key = createConnectQueryKey<I, O>(methodDescriptor, input);
  client.setQueryData(key, output);
}

It just provides a bit of type-safety for RPCs on top of QueryClient.setQueryData. Maybe we can add it to the package if it works out well.

paul-sachs commented 1 month ago

Although I think we could make it a little easier with a custom QueryClient, we do have a helper method to handle these kinds of things: createProtobufSafeUpdater.

We've been internally playing with the idea of exporting a custom QueryClient that provides common helper methods for interacting with the query client in a typesafe way but that will probably wait till everything has been moved over to protobuf-es@v2 anyways.