使用React Error Boundaries打破浏览器导航

时间:2018-01-05 21:49:10

标签: reactjs react-router browser-history

当我们的React 16代码库中出现错误时,它会被我们的顶级错误边界捕获。发生这种情况时,ErrorBoundary组件会愉快地呈现错误页面。

ErrorBoundary所在的位置

   return (
     <Provider store={configureStore()}>
       <ErrorBoundary>
         <Router history={browserHistory}>{routes}</Router>
       </ErrorBoundary>
     </Provider>
   )

但是,当使用浏览器后退按钮(单击一次)导航回来时,URL会在地址中更改,但页面不会更新。

我已尝试将错误边界向下移动到组件树中,但此问题仍然存在。

有关此问题所在的任何线索?

5 个答案:

答案 0 :(得分:18)

op现在可能已经找到了一个解决方案,但是为了其他任何有这个问题的人的利益,我将解释为什么我认为它正在发生以及如何解决它。

这可能是由于ErrorBoundary中的条件呈现呈现错误消息,即使历史记录已更改。

虽然上面没有显示,但是ErrorBoundary中的render方法可能类似于:

render() {
  if (this.state.hasError) {
    return <h1>An error has occurred.</h1>
  }

  return this.props.children;
}

componentDidCatch生命周期方法中设置了hasError。

一旦设置了ErrorBoundary中的状态,它将始终呈现错误消息,直到状态发生变化(上例中的hasError为false)。即使历史记录发生更改,也不会呈现子组件(在这种情况下为路由器组件)。

要解决此问题,请使用react-router withRouter更高阶的组件,通过包装ErrorBoundary的导出,使其可以通过props访问历史记录:

export default withRouter(ErrorBoundary);

在ErrorBoundary构造函数中从props中检索历史记录并设置处理程序以使用history.listen侦听对当前位置的更改。当位置发生变化(单击后退按钮等)时,如果组件处于错误状态,则会清除该位置,以便再次渲染子项。

const { history } = this.props;

history.listen((location, action) => {
  if (this.state.hasError) {
    this.setState({
      hasError: false,
    });
  }
});

答案 1 :(得分:1)

一旦检测到错误,我们需要在ErrorBoundary中重置错误状态。如下所示:

shouldComponentUpdate(nextProps, nextState) {
    if (this.state.hasError && nextState.hasError === false) {
      return false;
    }
    return true;
  }

render() {
    if (this.state.hasError) {
      this.setState({ hasError: false });
     // return fallback component to show on error
    }
    return this.props.children;
  }
}

答案 2 :(得分:0)

tl; dr 将组件包装在您希望出现错误且带有错误边界的地方,而不是整个树

首先使用withRouter尝试了@jdavies答案,但随后找到了我的用例Dan from the React-Team advised against using HOCs with Error Boundaries and rather use them at stragetic places的更好解决方案。

尽管在Twitter话题中围绕着利弊进行了辩论,但丹(Dan)公开了您应该走的路,但是我发现他的想法令人信服。

所以我要做的只是包裹那些我期望出错而不是整棵树的战略位置。对于我的用例,我更喜欢这样做,因为与以前相比,我可以抛出更具表现力的特定错误页面(出了错 vs 有一个身份验证错误)。

答案 3 :(得分:0)

要添加到上述jdavies的答案中,请确保您在componentDidMountuseEffect中注册了历史监听器(使用[]表示它没有依赖性),并且componentWillUnmountuseEffect返回语句中取消注册,否则可能会遇到在卸载的组件中调用setState的问题。

示例:

  componentDidMount() {
    this.unlisten = this.props.history.listen((location, action) => {
      if (this.state.hasError) {
        this.setState({ hasError: false });
      }
    });
  }

  componentWillUnmount() {
    this.unlisten();
  }

答案 4 :(得分:0)

jdavies 评论是要走的路,

但是,如果你对此感到困惑,基本上你可以让它看起来像这样:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    const { history } = props;
    history.listen((location, action) => {
      if (this.state["hasError"]) {
        this.setState({
          hasError: false,
        });
      }
    });
    this.state = { hasError: false };
  }

       ...

然后在您添加的文件末尾:

export default withRouter(ErrorBoundary);

(不要忘记在顶部 import { withRouter } from "react-router-dom";

此外,如果您使用的是例如export class ErrorBoundry ... 就像我一样,不要忘记在任何使用它的地方将 import { ErrorBoundary } from "./ErrorBoundry"; 更改为 import ErrorBoundary from "./ErrorBoundry";,例如应用程序.tsx