4

I have a component structured like this with my ErrorBoundary wrapping my Suspense element.

function App() {
  return (
    <ErrorBoundary fallback={<h2>Could not fetch cities.</h2>}>
      <Suspense fallback={<div>Loading..</div>}>
        <MyList />
      </Suspense>
    </ErrorBoundary>
  );
}

The MyList component includes an SWR data fetching hook as follows:

const { data } = useSwr(`/api/mydata`, fetcher, {
      suspense: true,
    });

My fetcher method throws an error as follows:

  const rsp = await fetch(url);
  if (rsp.ok) {
    return await rsp.json();
  } else {
    const MyError = function (message, status) {
      this.message = `${message} from url ${url} status code:${status}`;
      this.status = status;
    };
    throw new MyError(rsp.statusText, rsp.status);
  }
}

When the error happens, I don't know how to have my UI show the values thrown (that is, what is in the MyError class)

Peter Kellner
  • 14,748
  • 25
  • 102
  • 188
  • can you please update the code with your ErrorBoundary class? – Amila Senadheera Jan 10 '22 at 15:52
  • Probably the most tricky part is *how* to trigger the error to be checked, especially since the code is using `async\await`. Check my answer and let me know if this is OK for you. – Emanuele Scarabattoli Jan 10 '22 at 16:06
  • I don't think the answers below address my issue (I was not clear in my question enough). I want to include the actual error in the fallback component I define inside m App class. I'll update the question – Peter Kellner Jan 10 '22 at 16:13
  • Ah OK, please do include some references to `ErrorBoundary`, so the implementation code or some import, if it is imported from a library. – Emanuele Scarabattoli Jan 10 '22 at 16:14
  • You'll want to use https://github.com/bvaughn/react-error-boundary#readme & my answer here https://stackoverflow.com/questions/70621393/when-to-use-react-error-boundary-components/70621870#70621870 might help you. Without your implementation of `ErrorBoundary` it is difficult to see what you might be doing wrong. And, do read https://kentcdodds.com/blog/use-react-error-boundary-to-handle-errors-in-react – Sangeet Agarwal Jan 10 '22 at 17:04

4 Answers4

1

I'm not sure if there's some library you're using with a component named ErrorBoundary, but the way you would write your own to do this is something like the following:

class MyErrorBoundary extends React.Component {
  state = { error: null }

  static getDerivedStateFromError(error) {
    return { error };
  }

  render() {
    if (this.state.error) {
      // render whatever you like for the error case
      return <h2>{this.state.error.message}</h2>
    } else {
      return this.props.children
    }
  }
}
Nicholas Tower
  • 72,740
  • 7
  • 86
  • 98
0

According to the docs, you have access to error and errorInfo in componentDidCatch. You can set it to state of the ErrorBoundary. What you can do is use a third-party library (react-json-tree) to view the error nicely.

import JSONTree from 'react-json-tree';

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    this.setState({ error, errorInfo });
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <JSONTree data={this.state.error}/>;
    }

    return this.props.children; 
  }
}
Amila Senadheera
  • 12,229
  • 15
  • 27
  • 43
0

You should use this lifecycle in your ErrorBoundary component:

https://reactjs.org/docs/react-component.html#componentdidcatch

So something like (adapted from the documentation, to explain how to trigger the error in a way that is intercepted by ErrorBoundary):

// In ErrorBoundary

componentDidCatch(error, errorInfo) {
  this.setState({
    error: error,
    errorInfo: errorInfo
  });
}

// In MyList

buggyMethod() {
  fetch("something-wrong").then(error => this.setState({ error }));
}

render() {
 if(this.state.error){
   throw this.state.error;
 }
 return <span>Something cool!</span>;
}

Note

Seems little bit wired, but is the same technique used in the official documentation, "Live Demo" section:

https://reactjs.org/docs/error-boundaries.html#live-demo

Emanuele Scarabattoli
  • 4,103
  • 1
  • 12
  • 21
0

Here is the answer I was looking for:

fetcher.js

export async function fetcher(url) {
  const rsp = await fetch(url);
  if (rsp.ok) {
    return await rsp.json();
  } else {
    const MyError = function (message, status) {
      this.message = `${message} from url ${url} status code:${status}`;
      this.status = status;
    };
    throw new MyError(rsp.statusText, rsp.status);
  }
}

ErrorBoundary.js

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true, message: error?.message, status: error?.status };
  }

  render() {
    function addExtraProps(Component, extraProps) {
      return <Component.type {...Component.props} {...extraProps} />;
    }

    if (this.state.hasError) {
      return addExtraProps(this.props.fallback, {
        errorMessage: this.state.message,
        errorStatus: this.state.status,
      });
    }
    return this.props.children;
  }
}

And then the usage is something like this:

function CityLayout(props) {
  const { setSelectedCityId } = useContext(CityContext);
  return (
    <>
      <CityListMaxDDL />
      <CityList displayCount={5} />
      <CityDetail cityId={setSelectedCityId} />
    </>
  );
}

function App() {
  function MyErrorBoundaryFallback({ errorMessage, errorStatus }) {
    return (
      <div className="container">
        <h1>Error</h1>
        <div className="row">
          Error Status: <b>{errorStatus}</b>
        </div>
        <div className="row">
          ErrorMessage: <b>{errorMessage}</b>
        </div>
      </div>
    );
  }

  return (
    <ErrorBoundary fallback={<MyErrorBoundaryFallback />}>
      <Suspense fallback={<div>Loading..</div>}>
        <div className="container">
          <CityProvider>
            <CityLayout />
          </CityProvider>
        </div>
      </Suspense>
    </ErrorBoundary>
  );
Peter Kellner
  • 14,748
  • 25
  • 102
  • 188