Swr: Expire errored payload with `suspense: true`

Created on 28 Nov 2019  路  5Comments  路  Source: vercel/swr

I have an error boundary with a retry button that updates its own key to rerender the whole subtree.
I want to retry the failed request on rerender (I use shouldRetryOnError: false).

I tried to rethrow the error around useSWR. I get the logs, but on next render, the xhr request isn't refired, but SWR reraises the same error as before :(

  try {
    console.log("requesting", { key });
    result = useSWR(key, fetcher, { suspense: true });
  } catch (err) {
    console.log("error", { key });
    trigger(key);
    throw err;
  }

  const { data, isValidating, revalidate } = result;

Any pointer?

Thx a lot!

bug

Most helpful comment

Hi @bbenezech, I think the better approach is to clear out cached error in designated error boundary component
Here's a codesandbox example of approach I've been using

Couple points to mention:

  • if we caught a promise we're just need to re-throw it since it's a suspender
  • if we caught an actual error we save info about what key caused it, so we can clear cache entry later
  • in ErrorGuard component we clear cached entry when user clicks the button, but we could also just clear it in setState callback inside componentDidCatch method instead
  • we can also clear out all cached errors if we iterate over cached keys and check if they're error or not, but that seems kinda dangerous to me

All 5 comments

It seems like trigger doesn't work because here we don't get any updaters.
The reason we don't have any updaters is because they're set up in useLayoutEffect hook and in suspense mode we don't get to render and setup them if first request errors out

I think this particular issue could possibly be fixed if we set up errorKey's cache entry expiration before re-throwing it

Hi, since #231 got merged you can now clear up the cached error value on your own.
For your example it might look something like this:

import { cache } from 'swr';
// ...
try {
  console.log("requesting", { key });
  result = useSWR(key, fetcher, { suspense: true });
} catch (err) {
  console.log("error", { key });
  const [, , errorKey] = cache.serializeKey(key);
  cache.delete(errorKey, false);
  throw err;
}
// ...
const { data, isValidating, revalidate } = result;

Thanks a lot @nulladdict, you really helped me.

This works for me:

let result;
try {
  result = useSWR(key, fetcher, { suspense: true });
} catch (err) {
  const [, , errorKey] = cache.serializeKey(key);
  window.setTimeout(() => cache.delete(errorKey, false));
  throw err;
}
const { data, isValidating, revalidate } = result;

As you can see I have to delete the cache entry after rethrowing, otherwise it loops indefinitely.
The error promise is never received by my error boundary, useSWR justs goes nuts and short cuts the whole rerendering. Same with cache.__cache.delete.

Do you have any idea why deleting the entry from the cache could be somehow affecting Suspense's logic?

Hi @bbenezech, I think the better approach is to clear out cached error in designated error boundary component
Here's a codesandbox example of approach I've been using

Couple points to mention:

  • if we caught a promise we're just need to re-throw it since it's a suspender
  • if we caught an actual error we save info about what key caused it, so we can clear cache entry later
  • in ErrorGuard component we clear cached entry when user clicks the button, but we could also just clear it in setState callback inside componentDidCatch method instead
  • we can also clear out all cached errors if we iterate over cached keys and check if they're error or not, but that seems kinda dangerous to me

Awesome! I love this, much better. Works for me.
I was confused with the suspenders in the logs, thank you for the detailed explanation.

I think we should close this?

Was this page helpful?
0 / 5 - 0 ratings