React-apollo: [Hooks] subscribeToMore isn't memoized with useQuery

Created on 6 Aug 2019  路  5Comments  路  Source: apollographql/react-apollo

Intended outcome:

It would be great if useQuery could return observable query fields (subscribeToMore, ...) with a stable identity when the related observable didn't change. Do you think it's possible?

Actual outcome:

When I use the useQuery hook, the returned subscribeToMore function hasn't the same identity at each render. This might be because we use bind at this line:
https://github.com/apollographql/react-apollo/blob/d3918c2f03b50642453475e4f88259c476e2e907/packages/hooks/src/data/QueryData.ts#L214

How to reproduce the issue:

const { data, error, loading, subscribeToMore } = useQuery<GetTasks>(GET_TASKS);

useEffect(() => {
  const unsubscribe = subscribeToMore<TaskUpdated>({ document: TASK_UPDATED });
  return () => unsubscribe();
}, [subscribeToMore]);

As subscribeToMore changes at each render, we unsubscribe and subscribe again every time. We have to put [] as dependency list to prevent this behavior but it's not very resilient :/

Version

@apollo/[email protected]

Most helpful comment

Our workaround is to run the refetch in a custom hook - unnecessary if refetch has a stable identity. Posting here in case it helps someone out - big fan of the hooks API :)

const useRefetch = (refetch, { variables }) => {
    const callbackRef = useRef(refetch);

    useEffect(() => {
        callbackRef.current = refetch;
    }, [refetch]);

    useEffect(() => {
        callbackRef.current({ variables });
    }, [variables]);
};

All 5 comments

Having a similar issue with refetch - I store my query variables in react state, and trigger a refetch whenever the state changes. Would ideally like to add refetch to the dependency array, but this causes an infinite loop because every time refetch is called, it recreates the query variables (including "itself")

const [variables, setVariables] = React.useState({ keywords: "" });

const { loading, error, data = {}, refetch } = useQuery(SEARCH_ACCOUNTS);

useEffect(() => {
    refetch({ variables });
}, [variables]); // including refetch in the dependency array causes an infinite loop

Same issue here, we should make sure all of these callbacks: https://github.com/apollographql/react-apollo/blob/d3918c2f03b50642453475e4f88259c476e2e907/packages/hooks/src/data/QueryData.ts#L209L214 have a stable identity.

Kinda related to https://github.com/apollographql/react-apollo/issues/3260 (that has been resolved).

A workaround is to wrap subscribeToMore in useCallback and use the data returned by useQuery as dependency key.

Our workaround is to run the refetch in a custom hook - unnecessary if refetch has a stable identity. Posting here in case it helps someone out - big fan of the hooks API :)

const useRefetch = (refetch, { variables }) => {
    const callbackRef = useRef(refetch);

    useEffect(() => {
        callbackRef.current = refetch;
    }, [refetch]);

    useEffect(() => {
        callbackRef.current({ variables });
    }, [variables]);
};

Hello, one question. Will the callbacks change if variables change? Am I right that the current behavior is that they won't?

I'm wondering if it's better to return new callbacks. For example:

useEffect(() => {
    refetch();
}, [refetch]);

This would be ideal to me, however variables have to be put as a dependency even though they are not used.

@andycarrell's solution seems to hint that too.

Hey @alamothe I don't think they will change 馃榾 - Any reason not to pass variables to refetch?

useEffect(() => {
    refetch(variables);
}, [variables, refetch]);
Was this page helpful?
0 / 5 - 0 ratings