1

I am trying to make some reusable Apollo hooks and simplify the APIs with some encapsulation. So my idea is to have the useQuery hook inside of a custom hook. In my case I need actually the useLazyQuery hook:

export const useGetSomething = () => {
  const [something, setSomething] = useState<Something>();
  const [getSomething, { data, refetch }] =
    useLazyQuery<GetSomethingHook>(GET_SOMETHING_QUERY, {
      fetchPolicy: 'no-cache',
    });

  useEffect(() => {
    if (data?.something) {
      setSomething(data.something);
    }
  }, [setSomething, data]);

  const loadSomething = useCallback(
    (id: string, date: Date) => {
      getSomething({
        variables: {
          args: {
            id,
            date,
          },
        },
      });
    },
    [getSomething],
  );

  const refetchSomething = useCallback(
    (id: string, date: Date) => {
      refetch({
        variables: {
          args: {
            id,
            date,
          },
        },
      }).then((result) => setSomething(result.data.something))
    },
    [setSomething, refetch],
  );

  return useMemo(() => {
    return {
      loadSomething,
      refetchSomething,
      something,
    };
  }, [something, loadSomething, refetchSomething]);
};

So there is a range of issues:

  • if I use refetch with the same id and a new date, the useLazyQuery calls for the something with the old date that was the initial one (why? no idea...)
  • if I replace refetch with fetchMore in refetchSomething, the call is actually done properly AT FIRST, but then there is a weird another call without ANY actual source in that hook which calls for something with old date
  • if I use getSomething in both loadSomething and refetchSomething it works like fetchMore where even tho the function was called only once, the Network tab shows two calls - one with old and second with new data.

It just looks like Apollo is just broken.

Michał J. Gąsior
  • 1,457
  • 3
  • 21
  • 39

1 Answers1

-1

It's not that Apollo is broken, it's that you've what I call a lifecycle hell with useMemo and useCallback.

I'm not sure to understand why you are passing setSomething, getSomething and refetch as dependencies to your callbacks.

If you want loadSomething and refetchSomething to update with new args, then past your args props as dependencies, not your functions.

  • You need to provide every variable that changes to the dependency array as it is required by React hooks documentation. If you have linting turned on and you don't do that, you will get a linting error. And your comment is not really helpful here to be honest, because it still does not explain why this way it's wrong. Yes, I could use use hook arguments instead of function arguments, but there is no apparent reason why the second approach would not work it makes your life harder as the hook has to be positioned in a way to have access to those args during init, not as callback. – Michał J. Gąsior Aug 12 '22 at 14:11
  • Ludivin please refer to hooks documentation where it is said: "We recommend using the exhaustive-deps rule as part of our eslint-plugin-react-hooks package. It warns when dependencies are specified incorrectly and suggests a fix." – Michał J. Gąsior Aug 14 '22 at 08:23