reset error boundary on back journey using useErrorHandler hook from react-error-boundary
Asked Answered
R

3

11

I am using react-error-boundary package to show the fall back UI in case application throws any errors. The package works fine for me. I need to understand how to reset application error state if I go to previous pages using browser back button as going to previous pages also shows the fall back UI instead of original component. Is there anyway we can render the original component?

In below code user will be thrown error on Page2 since I am passing empty object as props. It will show fallback screen in that case. If I click on back button still fallback screen will be shown on Page1 as well which I don't want.

App.js

const errorHandler = (error) =>{
      console.log(error)
}

<BrowserRouter basename={'/bookingtool/obt/'}>
     <ErrorBoundary FallbackComponent={Fallback} onError={errorHandler}>
          <Routes>
               <Route path="/page1" element={<Page1 PageName="Page1" />} />
               <Route path="/page2" element={<Page2 PageName={{}} /> } />
          </Routes>
    <ErrorBoundary />
</BrowserRouter>

Page1.js

import { useErrorHandler } from 'react-error-boundary';
const Page1 = ({PageName}) =>{
     return(<p>{PageName}</p>)
}

Page2.js

import { useErrorHandler } from 'react-error-boundary';
const Page2 = ({PageName}) =>{
     return(<p>{PageName}</p>)
}

Fallback.js

import React from "react";

const Fallback= (props) => {
    return(<h1>Something went wrong</h1>)
}
Renzo answered 6/4, 2022 at 7:15 Comment(0)
H
32

Provide a key to <ErrorBoundary />. Whenever the key changes, the error boundary is reset.

In your case, using useLocation().pathname as key means it will reset whenever the path changes:

const location =  useLocation();

// ...
return (
  <ErrorBoundary key={location.pathname}>
    {/* ... */}
  </ErrorBoundary>
)

As a separate suggestion, I'd move the error handling inside a Layout component. This would allow keeping the navigation when an error happens, which is good UX.

A basic example here.

Horan answered 15/4, 2022 at 12:27 Comment(4)
This is awesome, thank you. Didn't realise about REB re-rendering happy path if the keys change. In my project your solution works just as well by just grabbing the pathname and passing that directly as key={location.pathname}, so the setState and useLayoutEffect don't seem to be needed. Are there any drawbacks to this?Heavenward
@Heavenward I agree with your observation. I just now observed that following this approach actually re-renders component every time. And your solution works perfectly fine for me. ThanksRenzo
This solution doesn't work well when using nested routes, due to re-renders. I hav an app with different pages (routing level 1), some of them contains tabbed sections (routing level 2). I wrap routing level 1 with error boundary component, when changing tabs (page containing tabs) then the whole page is re-rendered.Brendanbrenden
It is so sad that the documentation doesn't even mention this key thing...Airsick
P
7

Try to reset the error state when you navigate away:

const Fallback= ({ error, resetErrorBoundary} ) => {
    const location = useLocation();
    const errorLocation = useRef(location.pathname);
    useEffect(() => {
        if (location.pathname !== errorLocation.current) {
            resetErrorBoundary();
        }
    },[location.pathname])
    return(<h1>Something went wrong</h1>)
}
Paxon answered 14/4, 2022 at 20:14 Comment(1)
I think this is a better solution than the accepted one. I was having the same issue of the contents of error boundary (which was most of the app) getting re rendered on every url change as Mauro Nidola mentioned in a comment. This solution clears the error boundary but does not cause the side effect of re rendering the contents of the error boundary every time the url changesPhototypy
D
0

you can force re-render of error boundary using following way, first make a separate functional component that holds the error boundary and add listener to history

import { createBrowserHistory } from "history";
const history = createBrowserHistory();
//a fallback component
const ErrorFallback = () => {
 return <>
   <h1>Something went wrong.</h1>
   <button onClick={() => {
      history.back();
    }}>go back </button>
  </>
}
 
function RoutesContainer() {
 const [update, setUpdate] = useState(false);
 let navigate = useNavigate();
const historyChange = () => {
if (window.location.pathname !== history.location.pathname) {
  navigate(window.location.pathname, { replace: true });
}
};
useEffect(() => {
 history.listen(historyChange)
}, [historyChange])

 return <ErrorBoundary key={window.location.pathname}  FallbackComponent={ErrorFallback}>
<Routes>
  <Route path="/page1" element={<Page1 PageName="Page1" />} />
  <Route path="/page2" element={<Page2 PageName={{}} />} />
</Routes>
 </ErrorBoundary>
  }

at the time when you go back to the page1 from page2,history.location.pathname will have value "page2",since you press back,this value will not match to window.location.pathname,as window.location.pathname has value "page1",at this stage we will navigate to window.location.pathname,and use this value as key in our error boundary component.at the time of writing this answer i have used react-router-dom V6,and react v18

a complete demo of use case is here

    import React, { useEffect, useState } from 'react';
    import { BrowserRouter, Link, Route, Routes } from 'react-router-dom';
    import { createBrowserHistory } from "history";
    import { useNavigate } from "react-router-dom";
    import { ErrorBoundary } from 'react-error-boundary'

    const history = createBrowserHistory();

    const ErrorFallback = () => {
      return <>
        <h1>Something went wrong.</h1>
        <button onClick={() => {
          history.back();
        }}>go back </button>
      </>
    }
    const Page1 = ({ PageName }) => {
      return (<p>{PageName}
        <Link to={'/page2'} >page 2</Link>
      </p>)
    }

    const Page2 = ({ PageName }) => {
      return (<p>{PageName}</p>)
    }
    function RoutesContainer() {
      const [update, setUpdate] = useState(false);
      let navigate = useNavigate();

      const historyChange = () => {
        if (window.location.pathname !== history.location.pathname) {
          navigate(window.location.pathname, { replace: true });
        }
      };

      useEffect(() => {
        history.listen(historyChange)
      }, [historyChange])

      return <ErrorBoundary key={window.location.pathname}  FallbackComponent={ErrorFallback}>
        <Routes>
          <Route path="/page1" element={<Page1 PageName="Page1" />} />
          <Route path="/page2" element={<Page2 PageName={{}} />} />
        </Routes>
      </ErrorBoundary>
    }


    function App() {
      return (
        <BrowserRouter><RoutesContainer /></BrowserRouter>
      );
    }

    export default App;
Disavowal answered 15/4, 2022 at 19:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.