Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query keys with dynamic transports #418

Closed
Victor-Mestre-Sybo opened this issue Sep 26, 2024 · 11 comments
Closed

Query keys with dynamic transports #418

Victor-Mestre-Sybo opened this issue Sep 26, 2024 · 11 comments

Comments

@Victor-Mestre-Sybo
Copy link

Hello, first of all thanks for this amazing project!
We have this setup where our transport contains dynamic parameters, for example:

const transport = createConnectTransport({
  baseUrl: `/api/accounts/${accountId}/services`
})

So we have this issue where, when switching accountId the same cache will be used for a given query since the query key does not contain any information about the transport. We have considered either making our own abstraction of useQuery or calling something like queryClient.removeQueries() when the transport's baseUrl changes, is there any better alternative (like including this information in the query key)?

@timostamm
Copy link
Member

Thanks for filing the issue, Victor. callOptions are also not part of the query key.

The best workaround is probably to call createUseQueryOptions, modify the query key, and pass the options to useQuery from @tanstack/react-query.

@paul-sachs
Copy link
Collaborator

paul-sachs commented Sep 26, 2024

Another way to handle this would be to refetch the queries when accountId changes, something like:

const transport = createConnectTransport({
  baseUrl: `/api/accounts/${accountId}/services`
});
const queryClient = useQueryClient();
const previousAccountId = usePrevious(accountId);
useEffect(() => {
  if (previousAccountId !== undefined && previousAccountId !== accountId) {
    queryClient.refetchQueries();
  }
}, [accountId, previousAccountId]);

The above will refetch ALL queries, regardless of their transport but it can be tailored to target specific queries based on their query key.

edit

Perhaps a better solution to this would be to memoize the transport and have an effect triggered on the transport and triggering an effect based on that changing. That way it can also take into account all other options that could be provided to createConnectTransport like interceptors, etc.

@timostamm
Copy link
Member

With #437, we can make the Transport part of the query key. It will be important to create the transport in a module scope, or to memoize it. For example:

const transport = useMemo(() => createConnectTransport({
  baseUrl: `/api/accounts/${accountId}/services`,
}), [accountId]);

useQuery(say, {sentence: "hi"}, {transport});

Could be a footgun, but having to keeping track of reference is common in react.

@Victor-Mestre-Sybo
Copy link
Author

Victor-Mestre-Sybo commented Oct 4, 2024

Thank you for your suggestions!

@timostamm in order to address this issue using the new createTransportKey method, I'd still need to call createUseQueryOptions and modify the queryKey by prepending the transport key right?

@timostamm
Copy link
Member

@Victor-Mestre-Sybo, no, you do not need to modify the query key. You just have to keep track of your references. For example:

// broken, do not use
const transport = createConnectTransport({
  baseUrl: `/api/accounts/${accountId}/services`,
});

useQuery(say, {sentence: "hi"}, {transport});

Every time this code runs, a new transport is created, and the key will change.

The react hook useMemo "memoizes" the value. The factory function is called only once if the dependencies (accountId) remain the same:

const transport = useMemo(() => createConnectTransport({
  baseUrl: `/api/accounts/${accountId}/services`,
}), [accountId]);

useQuery(say, {sentence: "hi"}, {transport});

If accountId changes, a new transport is created, and a new query key is used.

@Victor-Mestre-Sybo
Copy link
Author

Sorry, I don't really understand it, I guess I'm missing something. How does createTransportKey come into play in your proposed solution?

As far as I understand, by doing useQuery(say, {sentence: "hi"}, {transport}); , even if a new transport is created, the query key does not depend on the transport, since this is what is used under the hood:
const queryKey = createConnectQueryKey(methodSig, input);

Therefore, the cache will be the same for a given key even after changing the transport right?

@timostamm
Copy link
Member

@Victor-Mestre-Sybo, this PR wires it up: #441

@Victor-Mestre-Sybo
Copy link
Author

@timostamm now it makes sense! it's looking really promising, thank you so much for addressing this issue!

@timostamm
Copy link
Member

@paul-sachs raised a good point: For SSR, you want to populate the query cache on the server, then use the that cache in the web browser (you prefetch queries, dehydrate the query client, and hydrate it again). For that to work, query keys must be the same on the server and in the web browser, which isn't guaranteed with createTransportKey (it counts references).

We're adding an escape hatch for SSR with #444.

We'll get a pre-release version 2 out as soon as possible, which solves this issue. Closing it.

@Victor-Mestre-Sybo
Copy link
Author

Thanks again! Regarding the issue you mention with SSR, it would have been helpful that the Transport object created with createConnectTransport method from @connectrpc/connect-web contained some unique parameter like the baseUrl itself. This way it could have been used as key, guaranteeing both uniqueness and same value in client and server. Just a thought.

@timostamm
Copy link
Member

We considered it. The transports from @connectrpc/connect-web also support interceptors, which can make arbitrary changes that affect caching. It isn't feasible to create a unique key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants