-
Notifications
You must be signed in to change notification settings - Fork 72
feat(react): Allow passing explicit client instance to React hooks
#114
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
base: main
Are you sure you want to change the base?
Conversation
|
I opened #112 a few hours before this with. I've been playing a bit with the idea and came across some caveats. A minor detail, changing the args on useQuery could potentially be breaking. Altough the arguments make no sense now so this is probably for the better. As is, the FuncionReference is not aware of the client context. So you could pass Neutral api functions as app api functions ance vice versa. Without getting any warnings at compile time: const neutralClient = useNeutralConvex();
// Both are valid TS but will fail at runtime
useQuery(api.custom.get, {}, neutralClient);
useQuery(api.neutral.get, {}); // in app contextI'm currently entertaining @convex-dev/react-query with @tanstack/react-query and exporting custom useQuery/useMutation/useActions. Like so import { convexQuery } from "@convex-dev/react-query";
import { useQuery, useQueryClient, UseQueryOptions, UseQueryResult } from "@tanstack/react-query";
import { OptionalRestArgsOrSkip } from "convex/react";
import { FilterApi, FunctionReference } from "convex/server";
import { api } from "../../convex/_generated/api";
type DotPaths<T> = {
[K in keyof T & string]:
T[K] extends FunctionReference<"query" | "mutation" | "action">
? K
: T[K] extends object
? `${K}.${DotPaths<T[K]>}`
: never
}[keyof T & string];
type ValueAtPath<T, P extends string> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? ValueAtPath<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
function getByDotPath<T, P extends DotPaths<T>>(
obj: T,
path: P,
): ValueAtPath<T, P> {
return path.split(".").reduce(
(acc, key) => acc?.[key],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
obj as any,
);
}
export type PublicQuery = FunctionReference<"query", "public">;
export type PublicQueryApi = FilterApi<typeof api, PublicQuery>;
export function useNeutralQueryByPath<
Path extends DotPaths<PublicQueryApi>,
Query extends ValueAtPath<PublicQueryApi, Path>
>(
queryPath: Path,
args: OptionalRestArgsOrSkip<Query>[0],
opts?: Omit<UseQueryOptions<Query["_returnType"], Error, Query["_returnType"]>, "queryKey" | "queryFn">
): UseQueryResult<Query["_returnType"]> {
const query = getByDotPath(api, queryPath);
return useNeuralQuery(query, args, opts);
}
function useNeuralQuery<Query extends PublicQuery>(
query: Query,
args: OptionalRestArgsOrSkip<Query>[0],
opts?: Omit<UseQueryOptions<Query["_returnType"], Error, Query["_returnType"]>, "queryKey" | "queryFn">
): UseQueryResult<Query["_returnType"]> {
const queryClient = useNeutralQueryClient();
return useQuery({
...convexQuery(query, args),
...opts,
}, queryClient);
}
// get the query client from the neutral context
function useNeutralQueryClient() {
return useQueryClient();
}Which ends up like with typesafe functions. useNeutralQueryByPath("custom.get", {}); // ❌
useNeutralQueryByPath("neutral.get", {}); // ✅
useQuery(api.neutral.get, {}); // ❌
useQuery(api.custom.get, {}); // ✅ |
|
The second arg is spread so that if there are no args, you don't have to pass {}, e.g. |
|
Another avenue here would be to have hooks be able to be generated via a factory: export const { useQuery, usePaginatedQuery, ... } = convexClientHooks(neutralClient);The default ones can infer from context. I can't recall if this is not supported by React hooks (I know they discourage dynamically created hooks, but maybe import-time generated ones like this are ok) |
|
if we add another parameter, it would likely be a catch-all const { status, value } = useQuery({ query: api.foo.bar, args: {..}, client: neutralClient }); |
What if I am using agent component in both of the backends, I can use useUIMessage and other hooks for the default convex backend but I can't use it for neutral backend, the proposed solution allows so useUIMessage and other hooks for components can also take client as argument and pass it to convex hooks
That implementation would be great if a default convex hooks can accept client |
This PR enables React applications to connect to multiple Convex backends simultaneously by allowing an optional
clientparameter on all React hooks.The Problem
See Issue #113 - React hooks implicitly use the nearest
ConvexProvidercontext, making multi-backend architectures impossible.The Solution
Updated the following React hooks to accept an optional
client?: ConvexReactClientparameter:useQueryuseQuery(query, args?, client?)useMutationuseMutation(mutation, client?)useActionuseAction(action, client?)useQueriesuseQueries(queries, client?)usePaginatedQueryusePaginatedQuery(query, args, options, client?)useConvexConnectionStateuseConvexConnectionState(client?)How It Works
clientis provided, the hook uses that specific client instanceclientisundefined, the hook falls back touseConvex()(the context provider)Usage Example (After)
Changes
src/react/client.tsuseQuery: Added optionalclientparameter, passes touseQueriesuseMutation: Added optionalclientparameter with fallback to contextuseAction: Added optionalclientparameter with fallback to contextuseConvexConnectionState: Added optionalclientparametersrc/react/use_queries.tsuseQueries: Added optionalclientparameter, usesclient ?? useConvex()src/react/use_paginated_query.tsusePaginatedQuery: Added optionalclientparameterusePaginatedQueryInternal: Added optionalclientparameter, passes touseQueriessrc/react/use_paginated_query2.tsusePaginatedQuery_experimental: Added optionalclientparameterBackward Compatibility
This change is fully backward compatible:
Testing
Files Modified
src/react/client.ts- useQuery, useMutation, useAction, useConvexConnectionStatesrc/react/use_queries.ts- useQueriessrc/react/use_paginated_query.ts- usePaginatedQuery, usePaginatedQueryInternalsrc/react/use_paginated_query2.ts- usePaginatedQuery_experimentalBy submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.