Xử lý lỗi là luôn là một phần quan trọng trong công việc phát triển Web, App ngày nay. Chỉ với một lỗi Javascript đơn giản có thể làm cho crash cả một trang web. Phổ biến nhất là trường hợp API trả về thiếu dữ liệu, làm cho quá trình tính toán của chúng ta bị sai lệch => crash cả ứng dụng 👿 .Trong quá khứ, React không cung cấp cho chúng ta một cách nào tiện lợi để giải quyết các vấn đề này, nhưng từ React 16 thì mọi thứ đã thay đổi, chúng ta có Error Boundary 😆 

Error Boundary là gì?

Error Boundary là một React component giúp bạn bắt lỗi Javascript ở bất cứ đâu trong component tree. Nếu Error Boundary bắt được lỗi, đầu tiên nó sẽ log lỗi, và sau đó sẽ hiển thị UI thông báo thay vì crash cả app.

Error boundary bắt được những lỗi gì?

Error Boundary bắt được hầu hết lỗi Javascript, nó hoạt động như try catch của javascript nhưng dành cho component. Mình đã viết một bài về xử lý lỗi hiệu quả trong Javascript, các bạn có thể đọc tại đây

Ở đây có một số lỗi mà Error Boundary không bắt được:

  • Lỗi sự kiện
  • Lỗi code bất đồng bộ
  • Server side rendering
  • Lỗi từ chính Boundary component

Error Boundary bắt các lỗi xảy ra trong quá trình rendering, trong vòng đời React và trong constructor của cả cây component dưới chúng.

Tạo ErrorBoundary component như thế nào

Một class component trở thành Error Boundary nếu nó được định nghĩa ít nhất 1 phương thức lifecycle là static getDerivedStateFromError() hoặc componentDidCatch().

  • Sử dụng static getDerivedStateFromError() để xử lý render UI dự phòng sau khi lỗi được quăng ra.
  • Sử dụng componentDidCatch() để log thông tin lỗi.
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 };
  }

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

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Sau đó bạn có thể sử dụng nó như một component thông thường

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Chỉ những class component mới có thể trở thành Error Boundary. Trong thực tế thì mình chỉ khai báo 1 Error Boundary duy nhất và dùng nó với mọi component xuyên suốt cả app.

Đặt Error Boundary ở đâu cho hợp lý?

Đặt bao quanh component của bạn! Dù bạn có thể đặt bao quanh các JSX element nhưng mình không khuyến khích sử dụng. Error Boundary sẽ render một UI mặc định trong trường hợp crash component để website được tiếp tục chạy.

Bạn cũng có thể bao quanh <App /> component trong index.js nếu bạn muốn try catch toàn bộ app. Nhưng nếu bạn dùng với react-router-dom thì mình khuyên không nên làm như vậy. Lý do mình sẽ giải thích phía dưới ngay đây :mrgreen:

Sử dụng Error Boundary với react-router-dom

Hiện tại bình thường khi sử dụng react-router-dom, app sẽ bị crash nếu người dùng click vào 1 link mà load component bị lỗi.

Mình sẽ ví dụ một trường hợp đơn giản với react-router-dom kết hơp Error Boundary ( codesandbox ở dưới bài nhé)

index.js

import React from "react";
import ReactDOM from "react-dom";

import App from "./App";
import { BrowserRouter } from "react-router-dom";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  rootElement
);

App.js

import React from "react";
import "./styles.css";
import { Link, Switch, Route } from "react-router-dom";
import Home from "./Home";
import Car from "./Car";
import Broken from "./Broken";

export default function App() {
  return (
    <div>
      <div className="nav">
        <Link to="/">Home</Link>
        <Link to="/car">Car</Link>
        <Link to="/broken">Broken</Link>
      </div>

      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/car" component={Car} />
        <Route path="/broken" component={Broken} />
      </Switch>
    </div>
  );
}

HomeCar component thì tương tự như thế này nhé, chỉ có tên là thay đổi thôi 😛

import React from "react";

export default function Home() {
  return <div className="title">Home</div>;
}

Broken component thì trông như thế này. Mình sẽ tạo một lỗi TypeError bằng cách a.b = 1 với a là null

import React, { useEffect } from "react";

export default function Broken() {
  useEffect(() => {
    const a = null;
    a.b = 1;
  }, []);

  return <div className="title">Broken</div>;
}

Và ta được giao diện như thế này 😎

Error Boundary React

Giao diện khi chạy App

Nếu bạn npm start, tiếp theo navigate đến /broken, app sẽ crash, đưa ra một UI dev error màu đỏ và dưới đó là một màn hình trắng. Cùng sử dụng ErrorBoundary component để fix vấn đề này nhé.

ErrorBoundary.js

import React from "react";
import { Link } from "react-router-dom";

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

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return (
        <div className="error-page">
          <h1>Something went wrong !</h1>
          <Link to="/">Home</Link>
        </div>
      );
    }

    return this.props.children;
  }
}

Với trường hợp này khi áp dụng Error Boundary anh em không nên để Error Boundary bao ngoài cả <App /> ngoài cùng. Vì làm như thế anh em sẽ mất đi tính năng chuyển trang của react-router-dom. Vì nhỡ đâu UI dự phòng của Error Boundary của anh em có link như bên trên thì sẽ không click được.

Không nên làm như thế này nhé anh em 😈 !

<ErrorBoundary>
  <App />
</ErrorBoundary>

Vậy làm như thế nào cho hợp lý?

Đặt bao ngoài Route

<Switch>
  <Route exact path="/" component="{Home}" />
  <Route path="/car" component="{Car}" />
  <ErrorBoundary>
    <Route path="/broken" component="{Broken}" />
  </ErrorBoundary>
</Switch>

Nếu làm cách này thì thoạt nhìn có vẻ ổn, cho ra UI đúng như yêu cầu

Error Boundary React

Click dấu x góc trên cùng bên phải sẽ có UI của Error Boundary

Nếu anh em đọc kĩ các dòng mờ ở hình trên thì overlay lỗi là tính năng của react-error-overlay. Nó chỉ xuất hiện ở màn hình dev, nó sẽ không hiện khi app bị crash trên production. Click dấu X trên cùng bên phải sẽ có UI của Error Boundary

Error Boundary React

UI của Error Boundary

 

Nhưng cách làm này có vấn đề là các Route khai báo phía dưới ErrorBoudary sẽ không hoạt động.  Nếu chúng ta chuyển khai báo Route /car xuống phía dưới thì khi click vào /car sẽ không có tác dụng nữa.

<Switch>
  <Route exact path="/" component="{Home}" />
  <ErrorBoundary>
    <Route path="/broken" component="{Broken}" />
  </ErrorBoundary>
  <Route path="/car" component="{Car}" />
</Switch>

Đặt ErrorBoundary bao gói component của bạn

Với cách này thì có 2 hướng tiếp cận đều cho lại hiệu quả như nhau nhé 🙄

Cách 1 thì anh em tạo 1 component bao ngoài Broken

const BrokenPage = () => (
  <ErrorBoundary>
    <Broken />
  </ErrorBoundary>
);
<Switch>
  <Route exact path="/" component="{Home}" />
  <Route path="/car" component="{Car}" />
  <Route path="/broken" component="{BrokenPage}" />
</Switch>

Cách 2 thì anh em bỏ vào prop render của Route

<Switch>
  <Route exact path="/" component="{Home}" />
  <Route path="/car" component="{Car}" />
  <Route path="/broken" render={(props) => (
  <ErrorBoundary>
    <Broken {...props} />
  </ErrorBoundary>
  )} />
</Switch>

Kết quả cho ra tương tự như đặt ErrorBoundary bao bên ngoài Route. Vậy bây giờ kiểm tra tiếp nếu ta khai báo Route /car xuống dưới ErrorBoudary thử xem có bị lỗi khi chuyển Route hay không nhé

<Switch>
  <Route exact path="/" component="{Home}" />
  <Route path="/broken" render={(props) => (
  <ErrorBoundary>
    <Broken {...props} />
  </ErrorBoundary>
  )} />
  <Route path="/car" component="{Car}" />
</Switch>

Kết /car vẫn hoạt động được. Mọi thứ đúng mong đợi của chúng ta.

Error Boundary React

Kết quả đúng như mong đợi

Vậy nên hãy dùng cách này nhé anh em! Chân lý đây rồi 😆

Trong thực tế, hãy dùng ErrorBoundary bao hết các component khi khai báo Route để đảm bảo app của chúng ta được catch lỗi một cách toàn diện, ở trên mình chỉ bao mỗi Broken để test thôi.

Về vấn đề hiệu suất khi dùng Error Boundary thì anh em cũng đừng lo vì nó hoạt động như try catch Javascript. Try catch Javascript đem lại hiệu suất tương đương như code thông thường ở tất cả các trình duyệt ngày nay.

Hết bài rồi đó, bài hôm nay ngắn vậy thôi. Chủ yếu giới thiệu anh em về Error Boundary – một component khá hay mà nhiều anh em mình thấy chưa áp dụng, cũng như đưa ra một số trường hợp trong thực tếmà anh em gặp phải. Nếu ace thấy hay thì hãy để lại 1 comment cho mình biết với nhá :mrgreen:

click vào đây nếu trình duyệt không hiển thị codesandbox


Tham khảo
Doc Error Boundaries

React Error Boundaries