#javascript #graphql #apollo #hooks

Custom useQuery hook for Apollo

Disclaimer: this was initially posted as a tweet, I’m retroactively posting this as an article for better searchability.

The problem

I’m transitioning a React codebase to use hooks when possible/necessary since it greatly helps simplify some areas, and one of the blockers we have on my team is that Apollo does not yet support hooks.

The Apollo team is working on this, but I didn’t want to use their solution just yet since it is in beta (and still has some rough edges as you can see on their Github repo issues). That would require installing a non-stable version of the library, which is not acceptable for this particular app.

I also didn’t want to wait until hooks support is stable, or to use this compatible alternative (which looks pretty good tbh, but not tested enough).

So I went ahead and wrote this hook that provides enough functionality to take away the pain I’m having using render props and allow us to migrate our components to use hooks.

/**
 * React hook to execute an Apollo query and keep the data up to date according
 * what's newest on the cache.
 *
 * @param { DocumentNode } query: the GraphQL query you want to run
 * @param { Object } variables: the variables you want to interpolate on the query
 * Note: you may want to memoize your object to prevent the query to run multiple times.
 *
 * Read more about param types on Apollo's watchQuery docs:
 * https://www.apollographql.com/docs/react/api/apollo-client/#ApolloClient.watchQuery

 * @returns { Object } an object with 3 properties:
 * { data: (or undefined), loading: whether is loading or not, error: (or undefined) }
 */
function useQuery(query, variables) {
  const [state, setState] = useState({ loading: true });

  const handleNext = ({ data }) => setState({ data, loading: false });
  const handleError = (error) => setState({ error, loading: false });

  useEffect(() => {
    // get client created for ApolloProvider
    const client = getApolloClient();

    const observableQuery = client.watchQuery({ query, variables });
    const subscription = observableQuery.subscribe({
      next: handleNext,
      error: handleError,
    });

    return () => subscription.unsubscribe();
  }, [query, variables]);

  return { ...state };
}

This is not perfect, but it solves the blocker for us.