Apollo Client lazy refetch
Asked Answered
B

3

6

In Apollo Client v3 React implementation, I am using hooks to use subscription. When I receive data from subscription I would like to refetch query but only if query has been previously executed and is in cache. Is there a way to achieve this?

I have started by having a lazy query and then checking the cache manually when subscription data received and then trying to execute lazy query and refetch. It works but it just feels clunky...

export const useMyStuffLazyRefetch = () => {
    const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
    const client = useApolloClient();
    const [getMyStuff, { data, refetch }] = useLazyQuery<IStuffData>(GET_MY_STUFF);

    useEffect(() => {
        if (refetchNeeded) {
            setRefetchNeeded(false);
            refetch();
        }
    }, [refetchNeeded]);

    const refetchIfNeeded = async () => {
        const stuffData = client.cache.readQuery<IStuffData>({ query: GET_MY_STUFF });
        if (!stuffData?.myStuff?.length) return;
        getMyStuff();
        setRefetchNeeded(true);
    }

    return {
        refetchIfNeeded: refetchIfNeeded
    };
}
Bushore answered 29/6, 2020 at 6:24 Comment(0)
B
0

In case this can help to somebody. I have created a separate hook so the usage is less of an eyesore.

This is the hook to refetch if data is in cache. If the data is not in the cache, Apollo Client errors instead of returning something like undefined or null

import { useState, useEffect } from "react";
import { OperationVariables, DocumentNode, LazyQueryHookOptions, useApolloClient, useLazyQuery } from "@apollo/client";

export default function useLazyRefetch <TData = any, TVariables = OperationVariables>(query: DocumentNode, options?: LazyQueryHookOptions<TData, TVariables>) {
    const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
    const [loadData, { refetch }] = useLazyQuery(query, options);
    const client = useApolloClient();

    useEffect(() => {
        if (refetchNeeded) {
            setRefetchNeeded(false);
            refetch();
        }
    }, [refetchNeeded]);

    const refetchIfNeeded = (variables: TVariables) => {
        try {
            const cachecData = client.cache.readQuery<
                TData,
                TVariables
            >({
                query: query,
                variables: variables
            });
            if (!cachecData) return;
            loadData({ variables: variables });
            setRefetchNeeded(true);
        }
        catch {}
    };

    return {
        refetchIfNeeded: refetchIfNeeded
    };
}

And the hook usage example:

const { refetchIfNeeded } = useLazyRefetch<
        IStuffData,
        { dataId?: string }
    >(GET_MY_STUFF);

//... And then you can just call it when you need to

refetchIfNeeded({ dataId: "foo" });
Bushore answered 29/6, 2020 at 20:41 Comment(0)
T
5

useLazyQuery has a prop called called, this is a boolean indicating if the query function has been called,

so maybe you can try this:

export const useMyStuffLazyRefetch = () => {
    const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
    const client = useApolloClient();
    const [getMyStuff, { data, refetch, called }] = useLazyQuery<IStuffData>(GET_MY_STUFF);

    useEffect(() => {
        if (refetchNeeded) {
            setRefetchNeeded(false);

            if (called) {
              refetch();
            }
            else {
              getMyStuff()
            }
        }
    }, [refetchNeeded, called]);

    const refetchIfNeeded = async () => {
        const stuffData = client.cache.readQuery<IStuffData>({ query: GET_MY_STUFF });
        if (!stuffData?.myStuff?.length) return;
        getMyStuff();
        setRefetchNeeded(true);
    }

    return {
        refetchIfNeeded: refetchIfNeeded
    };
}
Tanning answered 29/6, 2020 at 7:21 Comment(1)
Thank you for your answer. Not sure if the change would have much effect, the refetch is only triggered on a state change and at that point, refetchIfNeeded function has already called the getMyStuff() so I think the called check would be redundant? Another component would have potentially populated the cache so useMyStuffLazyRefetch would only be responsible for calling refetch but should not try to call server if data is not in a cache.Bushore
B
0

In case this can help to somebody. I have created a separate hook so the usage is less of an eyesore.

This is the hook to refetch if data is in cache. If the data is not in the cache, Apollo Client errors instead of returning something like undefined or null

import { useState, useEffect } from "react";
import { OperationVariables, DocumentNode, LazyQueryHookOptions, useApolloClient, useLazyQuery } from "@apollo/client";

export default function useLazyRefetch <TData = any, TVariables = OperationVariables>(query: DocumentNode, options?: LazyQueryHookOptions<TData, TVariables>) {
    const [refetchNeeded, setRefetchNeeded] = useState<boolean>(false);
    const [loadData, { refetch }] = useLazyQuery(query, options);
    const client = useApolloClient();

    useEffect(() => {
        if (refetchNeeded) {
            setRefetchNeeded(false);
            refetch();
        }
    }, [refetchNeeded]);

    const refetchIfNeeded = (variables: TVariables) => {
        try {
            const cachecData = client.cache.readQuery<
                TData,
                TVariables
            >({
                query: query,
                variables: variables
            });
            if (!cachecData) return;
            loadData({ variables: variables });
            setRefetchNeeded(true);
        }
        catch {}
    };

    return {
        refetchIfNeeded: refetchIfNeeded
    };
}

And the hook usage example:

const { refetchIfNeeded } = useLazyRefetch<
        IStuffData,
        { dataId?: string }
    >(GET_MY_STUFF);

//... And then you can just call it when you need to

refetchIfNeeded({ dataId: "foo" });
Bushore answered 29/6, 2020 at 20:41 Comment(0)
B
0

typescript is complaining in your

   useEffect(() => {
   if (refetchNeeded) {
       setRefetchNeeded(false);
       refetch();
   }
}, [refetchNeeded]);

refetch() says - Cannot invoke an object which is possibly 'undefined'.ts(2722)

const refetch: ((variables?: Partial<TVariables> | undefined) => Promise<ApolloQueryResult<TData>>) | undefined

    and in [refetchNeeded] dependency - 

React Hook useEffect has a missing dependency: 'refetch'. Either include it or remove the dependency array.eslintreact-hooks/exhaustive-deps const refetchNeeded: boolean

Brantley answered 17/7, 2021 at 11:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.