skip to Main Content

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!

2

Answers


  1. 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

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search