react-apollo icon indicating copy to clipboard operation
react-apollo copied to clipboard

useQuery() refetch throws on error instead of flowing through hook

Open atombender opened this issue 5 years ago • 7 comments

Consider something like:

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

if (error) {
  return (
    <span>
      Could not fetch that.
      <button onClick={() => refetch()}>Try again</button>
    </span>
  )
}

return ...

If the query fails, the error message will be shown. But the provided refetch() function does not integrate with the hook, and will in fact throw an exception on further failure. I expected errors to flow back into the hook, but this is not the case. Further, refetch() does not update loading or networkStatus.

This means any component using refetch will have to handle errors (and retrying) in two different and incompatible ways. Perhaps something like:

const [refetchError, setRefetchError] = useState<any>(undefined)
const [retrying, setRetrying] = useState<boolean>(false)
const {loading, error, data, refetch} = useQuery(someQuery)

if (error || refetchError) {
  return (
    <span>
      Could not fetch that.
      <button onClick={() => {
        setRetrying(true)
        try {
          refetch()
        } catch (err) {
          setRefetchError(err)
        } finally {
          setRetrying(true)
        }
      }}>Try again</button>
    </span>
  )
}

return ...

This is ugly.

atombender avatar Oct 07 '19 20:10 atombender

It appears that setting notifyOnNetworkStatusChange: true fixes this — error and load status flows back to the hook. Is this a bug? This is, at least, not obvious from the documentation.

atombender avatar Oct 07 '19 20:10 atombender

It looks like it throws when using network-only fetch policy, but flows through the hook when using cache-and-network. I don't see this behavior documented anywhere so it's hard to tell if it's on purpose

dylanwulf avatar Oct 10 '19 21:10 dylanwulf

I'm also running into this issue. In our case, above mentioned proposals did not result in the data flowing through the hook. We call refetch with a new set of variables as outlined in the docs, but the result of this is not reflected in the data or loading fields from the useQuery hook.

However, calling refetch is returning a promise which resolves into a query result ({data, loading, error etc..}). this would be an unwieldy way of making a hook implementation work since it would requires us to save this new result in a new state field, duplicating our data fetching logic.. 🤷‍♂

Would it be nice to outline this use-case (refetching data icm useQuery) and the best practices in the docs?

pepf avatar Oct 28 '19 16:10 pepf

+1 can confirm that refetch() throwing does not update the error returned by useQuery, does not seem to be affected by fetchPolicy or notifyNetworkStatusChange for me.

This makes doing retry on errors very difficult.

The example by the original poster would break if the props that useQuery used for its variables changed triggering a new query as loading or error would be out of step with retrying and refetchError. You would need to add an additional useEffect that cleared those fields when any query relevant props change.

"@apollo/react-hooks": "3.1.3"

georeith avatar Jan 16 '20 20:01 georeith

I wrote a workaround for this:

// useQuery has a bug where an error in refetch does not change its error property
export default function useRetryableQuery(query, options = {}) {
  const [refetchError, setRefetchError] = useState(undefined);
  const result = useQuery(query, options);
  const variables = options.variables || {};
  useEffect(() => {
    setRefetchError(undefined);
  }, Object.values(variables));

  return {
    ...result,
    networkStatus: refetchError ? 8 : result.networkStatus,
    error: refetchError || result.error,
    refetch: () => {
      setRefetchError(undefined);
      return result.refetch().catch(err => {
        setRefetchError(err);
        return Promise.reject(err);
      });
    },
  };
}

Unfortunately there is another bug in useQuery that after the first error it doesn't seem to trigger any more be it from a change in variables or a refetch() call. I am unable to work around that without reimplementing useQuery.

https://github.com/apollographql/react-apollo/issues/3800

georeith avatar Jan 17 '20 10:01 georeith

@hwillson can you take a look at this? It's a pretty important issue. Not being able to handle errors correctly in Apollo is frustrating.

lifeiscontent avatar Jun 08 '20 23:06 lifeiscontent

I'm seeing this error with useMutation and the fact I can't catch an error in a mutation is super hard to provide a solid UX for the users of my application.

lifeiscontent avatar Jun 08 '20 23:06 lifeiscontent