query icon indicating copy to clipboard operation
query copied to clipboard

Type Error when using `useQueries` with results returning different types, generated from an array

Open todor-a opened this issue 1 year ago • 3 comments

Describe the bug

I want to use useQueries to fetch x queries every time, and another y queries, whose count is not known at compile time.

Here is a short example (playground below):

export const useThings = () => {
    const foos = [1, 2, 3].map(() => ({ ...FooQueries.get() }));

    return useQueries({
        queries: [...foos, { ...BarQueries.get(), }],
    });
};

There is a type error that says:

The type 'QueryKey' is 'readonly' and cannot be assigned to the mutable type 'string[]'

Your minimal, reproducible example

https://tsplay.dev/mq2d2W

Steps to reproduce

use useQueries with a variable count of queries that have different return type than other queries

Expected behavior

Expected to be able to pass an array of queries with variable length to useQueries, along with other queries with different return type.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • MacOS

Tanstack Query adapter

None

TanStack Query version

5.52.1

TypeScript version

5.5.4

Additional context

Edit: I tinkered a bit more with this and it appears that the issue is with the queryOptions function. Here is the full error:

Type '{ queryKey: string[] & { [dataTagSymbol]: number; }; enabled?: Enabled<number, Error, number, string[]> | undefined; staleTime?: StaleTime<number, Error, number, string[]> | undefined; ... 25 more ...; maxPages?: number | undefined; }' is not assignable to type 'UseQueryOptionsForUseQueries<unknown, Error, unknown, QueryKey>'.
  Type '{ queryKey: string[] & { [dataTagSymbol]: number; }; enabled?: Enabled<number, Error, number, string[]> | undefined; staleTime?: StaleTime<number, Error, number, string[]> | undefined; ... 25 more ...; maxPages?: number | undefined; }' is not assignable to type 'OmitKeyof<UseQueryOptions<unknown, Error, unknown, QueryKey>, "placeholderData">'.
    Types of property 'enabled' are incompatible.
      Type 'Enabled<number, Error, number, string[]> | undefined' is not assignable to type 'Enabled<unknown, Error, unknown, QueryKey> | undefined'.
        Type '(query: Query<number, Error, number, string[]>) => boolean' is not assignable to type 'Enabled<unknown, Error, unknown, QueryKey> | undefined'.
          Type '(query: Query<number, Error, number, string[]>) => boolean' is not assignable to type '(query: Query<unknown, Error, unknown, QueryKey>) => boolean'.
            Types of parameters 'query' and 'query' are incompatible.
              Type 'Query<unknown, Error, unknown, QueryKey>' is not assignable to type 'Query<number, Error, number, string[]>'.
                Types of property 'queryKey' are incompatible.
                  The type 'QueryKey' is 'readonly' and cannot be assigned to the mutable type 'string[]'.ts(2322)

Edit 2: If all queries return the same type of result, it works.

todor-a avatar Aug 29 '24 05:08 todor-a

different return types generally work, I think this is rather an issue with queryOptions. If we remove queryOptions, it also works. playground.

Or rather, a combination of the two:

  • queryOptions for BarQueries returns the type UseQueryOptions<boolean, Error, boolean, string[]>, so the TQueryKey type parameter is inferred to be string[]
  • useQueries infers the type to be UseQueryOptionsForUseQueries<unknown, Error, unknown, QueryKey>. Here, the TQueryKey type parameter is inferred to QueryKey, which is our upper bound, and that is readonly unknown[]

So it seems the readonly here makes all the difference.

But even if we get rid of that by making our keys of type QueryKey, we get a different error:

Type 'QueryFunction<unknown, QueryKey, never>' is not assignable to type 'QueryFunction<number, QueryKey, never>'.
  Type 'unknown' is not assignable to type 'number'.(2322)

This is again because inference of UseQueryOptionsForUseQueries, where data falls back to unknown.

Thinking about it some more - I don't think variadic queries of different types is something that we can infer / support. For variadic queries, they have to be the same type. Static queries (tuples) of different types are supported.

In your example, ideally the last element in the result array would be typed as bar, and all the others as foo. But I think this is way to complex to achieve, which is why we fallback to unknown, and that then doesn't really work with queryOptions.

I think you have to do manual type annotations for this one. Like, what should the data type of useThings be? number | boolean - a union of what all queries can return, or just unknown?

TkDodo avatar Aug 29 '24 07:08 TkDodo

I agree that typing this will be way too complex.

To answer your question, I think that the type can be number | boolean[], if the call looks like this (the spread is at the end):

export const useThings = () => {
    const foos = [1, 2, 3].map(() => ({ ...FooQueries.get() }));

    return useQueries({
        queries: [{ ...BarQueries.get() }, ...foos],
    });
};

todor-a avatar Aug 29 '24 08:08 todor-a

if you want to make that work, please take a stab at it.

TkDodo avatar Sep 06 '24 05:09 TkDodo