RJ
RJ9mo ago

Spooky types

Any TypeScript pros out there understand this behavior?
import {
OptionalRestArgsOrSkip,
useQuery,
} from "convex/react";

...

useQuery(anyQuery, "skip");
// ✅ All good

const skip: OptionalRestArgsOrSkip<any> = "skip";
// ❌ Type 'string' is not assignable to type '[args?: "skip" | EmptyObject | undefined] | [args: any]'.
useQuery(anyQuery, skip);
// ❌ Argument of type '[OptionalRestArgsOrSkip<Query>]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

const skipAny: OptionalRestArgsOrSkip<any> = null as any;
// ✅ All good (it better be)
useQuery(anyQuery, skipAny);
// ❌ Argument of type '[[args?: "skip" | EmptyObject | undefined] | [args: any]]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

// The type signature of `useQuery` is this:
function useQuery<Query extends FunctionReference<"query">>(
query: Query,
...args: OptionalRestArgsOrSkip<Query>
): Query["_returnType"] | undefined
import {
OptionalRestArgsOrSkip,
useQuery,
} from "convex/react";

...

useQuery(anyQuery, "skip");
// ✅ All good

const skip: OptionalRestArgsOrSkip<any> = "skip";
// ❌ Type 'string' is not assignable to type '[args?: "skip" | EmptyObject | undefined] | [args: any]'.
useQuery(anyQuery, skip);
// ❌ Argument of type '[OptionalRestArgsOrSkip<Query>]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

const skipAny: OptionalRestArgsOrSkip<any> = null as any;
// ✅ All good (it better be)
useQuery(anyQuery, skipAny);
// ❌ Argument of type '[[args?: "skip" | EmptyObject | undefined] | [args: any]]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

// The type signature of `useQuery` is this:
function useQuery<Query extends FunctionReference<"query">>(
query: Query,
...args: OptionalRestArgsOrSkip<Query>
): Query["_returnType"] | undefined
12 Replies
RJ
RJOP9mo ago
Psuedo-code-ish but I can also paste a reproducible example
ian
ian9mo ago
"skip" as const is my guess
RJ
RJOP9mo ago
@ian
const skip: OptionalRestArgsOrSkip<any> = "skip" as const;
// ❌ Type 'string' is not assignable to type '[args?: "skip" | EmptyObject | undefined] | [args: any]'.
const skip: OptionalRestArgsOrSkip<any> = "skip" as const;
// ❌ Type 'string' is not assignable to type '[args?: "skip" | EmptyObject | undefined] | [args: any]'.
!
ian
ian9mo ago
ah, I think you would need skip to be ["skip"] and pass it in as ...skip the optional rest args is an array to handle varargs
RJ
RJOP9mo ago
What I actually want to do is have a const which can be either "skip" or the args Maybe spreading would work either way? I figured it has something to do with the rest param business, but I don't quite understand it
ian
ian9mo ago
Yeah spreading allows you to have 1 arg when args are required, and 0 args when your args are {}. So it's either ["skip"] [args] or [] but you could maybe just have the type of OptionalRestArgsOrSkip<any>[0] without the array gotta run
RJ
RJOP9mo ago
Hmm
ballingt
ballingt9mo ago
Side note, these fancy types are mostly for allowing useQuery(api.takesNoArgs) but disallowing useQuery(api.takesArgs). If you're writing your own hooks or wrappers you can simplify this if you're willing to disallow the useQuery() syntax with a single argument.
RJ
RJOP9mo ago
I'm probably just going to give up on typing this the safe way ultimately, but forget about inference—I can't even figure out what type TypeScript expects here.
const useF = <Query extends FunctionReference<"query">>(query: Query) => {
const args1: OptionalRestArgsOrSkip<Query> =
null as unknown as OptionalRestArgsOrSkip<Query>;

type UseQueryArgs =
Parameters<typeof useQuery<any>> extends [infer _Query, ...infer Args]
? Args
: never;
const args2: UseQueryArgs = null as unknown as UseQueryArgs;

useQuery(query, args1);
// ❌ Argument of type '[OptionalRestArgsOrSkip<Query>]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

useQuery(query, ...args1);
// ❌ Argument of type '[...OptionalRestArgsOrSkip<Query>]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

useQuery(query, args2);
// ❌ Argument of type '[[args: any] | [args?: "skip" | EmptyObject | undefined]]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

useQuery(query, ...args2);
// ❌ Argument of type '[args: any] | [args?: "skip" | EmptyObject | undefined]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.
// Type '[args?: "skip" | EmptyObject | undefined]' is not assignable to type 'OptionalRestArgsOrSkip<Query>'.
};
const useF = <Query extends FunctionReference<"query">>(query: Query) => {
const args1: OptionalRestArgsOrSkip<Query> =
null as unknown as OptionalRestArgsOrSkip<Query>;

type UseQueryArgs =
Parameters<typeof useQuery<any>> extends [infer _Query, ...infer Args]
? Args
: never;
const args2: UseQueryArgs = null as unknown as UseQueryArgs;

useQuery(query, args1);
// ❌ Argument of type '[OptionalRestArgsOrSkip<Query>]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

useQuery(query, ...args1);
// ❌ Argument of type '[...OptionalRestArgsOrSkip<Query>]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

useQuery(query, args2);
// ❌ Argument of type '[[args: any] | [args?: "skip" | EmptyObject | undefined]]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.

useQuery(query, ...args2);
// ❌ Argument of type '[args: any] | [args?: "skip" | EmptyObject | undefined]' is not assignable to parameter of type 'OptionalRestArgsOrSkip<Query>'.
// Type '[args?: "skip" | EmptyObject | undefined]' is not assignable to type 'OptionalRestArgsOrSkip<Query>'.
};
Should I know this? Maybe I'm just a noob?
ballingt
ballingt9mo ago
Nah this took me a while to figure out last week What's the goal here? I may be able to write up an example
RJ
RJOP9mo ago
I'm writing a variant of the usePaginatedQuery hook which only holds results for one page at a time, but keeps track of previous cursors to support navigating forward and backwards through adjacent pages. Here's what I have so far (it's not finished)
RJ
RJOP9mo ago
See the mergeArgs function And directly below

Did you find this page helpful?