Trouble correctly inferring type with Typescript and React Query

31 Views Asked by At

I have a React component that looks like this (highly simplified code):

const TestComponent = () => {

    const { data: stuff, isLoading: isLoadingStuff, isSuccess: hasLoadedStuff } = useStuff();

    // ... other similar RQ query wrapper hooks

    const isLoading = isLoadingStuff && isLoadingOtherStuff;
    const loaded = hasLoadedStuff && hasLoadedOtherStuff;

    return (
        <div>
            <Loading isLoading={isLoading} />
            {loaded && (
                <Stuff stuff={stuff} />
                <OtherStuff otherStuff={otherStuff} />    
            )}
        </div>
    )
}

I have a number of RQ queries wrapped in custom hooks (like useStuff). For increased readability I have extracted and renamed the values I'm interested in using object destructuring. However TS doesn't seem to be able to infer that if loading === true then stuff cannot be undefined and so I get the following error for stuff={stuff}:

TS2322: Type string[] | undefined is not assignable to type string[]
Type undefined is not assignable to type string[]

Of course if I revert to

const stuff = useStuff();
const isLoading = stuff.isLoading && otherStuff.isLoading
// and then
<Stuff stuff={stuff.data} />

then it works fine.

Is there a way around this or must I resign myself to the second approach?

Many thanks!

UPDATE - Ideally, I'd like to be able to return a renamed subset of RQ params from my custom hook (usually just data, isLoading, isSuccess) but I'm running into the same type inference issues...

2

There are 2 best solutions below

1
leonat On BEST ANSWER

check this playground. i recreated your example and it works fine, but you need to use discriminated union types in your hook implementation. maybe you are not running the latest typescript version? this type of tracking and inference was introduced not long ago.

0
Titian Cernicova-Dragomir On

You just need to add a discriminated union as the return type of your hooks. In recent versions, typescript can follow discriminated unions though both destructuring and indirection (if assigned to const variables)

type HookResult<T> = 
    | {isLoading: true, data: undefined, isSuccess: undefined}
    | {isLoading: false, isSuccess: false, data: undefined}
    | {isLoading: false, isSuccess: true, data: T } 

function useStuff() : HookResult<Stuff> {
   ///...
}


function useOtherStuff() : HookResult<OtherStuff> {
   ///...
}

const TestComponent = () => {

    const { data: stuff, isLoading: isLoadingStuff, isSuccess: hasLoadedStuff } = useStuff();
    const { data: otherStuff, isLoading: isLoadingOtherStuff, isSuccess: hasLoadedOtherStuff } = useOtherStuff();

    const isLoading = isLoadingStuff && isLoadingOtherStuff;
    const loaded = hasLoadedStuff && hasLoadedOtherStuff;

    return (
        <div>
            <Loading isLoading={isLoading} />
            {loaded && (<>
                <Stuff stuff={stuff} />
                <OtherStuff otherStuff={otherStuff} />    
            </>)}
        </div>
    )
}

Playground Link