18

Here is my current attempt on how to properly type a React ErrorBoundary class component in Typescript:

import React from "react";
import ErrorPage from "./ErrorPage";
import type { Store } from "redux";   // I'M PASSING THE REDUX STORE AS A CUSTOM PROP

interface Props {
  store: Store         // THIS IS A CUSTOM PROP THAT I'M PASSING
}

interface State {      // IS THIS THE CORRECT TYPE FOR THE state ?
  hasError: boolean
}

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

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

  componentDidCatch(error, errorInfo: React.ErrorInfo): void {
    // You can also log the error to an error reporting service
    // logErrorToMyService(error, errorInfo);
    console.log("error: " + error);
    console.log("errorInfo: " + JSON.stringify(errorInfo));
    console.log("componentStack: " + errorInfo.componentStack);
  }

  render(): React.ReactNode {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return(
        <ErrorPage
          message={"Something went wrong..."}
        />
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

This question is about the types for this ErrorBoundary class component. I'm breaking it into parts to make it easier.


PART A: Types for props and state

Is what I'm doing right?

interface Props {
  store: Store         // THIS IS A CUSTOM PROP THAT I'M PASSING
}

interface State {      // IS THIS THE CORRECT TYPE FOR THE state ?
  hasError: boolean
}

class ErrorBoundary extends React.Component<Props,State> {}

PART B: getDerivedStateFromError(error)

What type should I choose for the error parameter? The return type should be the State type, right?

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

PART C: componentDidCatch(error, errorInfo: React.React.ErrorInfo)

What type should I choose for the error parameter? For the errorInfo, React does have a ErrorInfo type that seems to be correct. Is it? The return type should be void, correct?

componentDidCatch(error, errorInfo: React.ErrorInfo): void {
    console.log("error: " + error);
    console.log("errorInfo: " + JSON.stringify(errorInfo));
    console.log("componentStack: " + errorInfo.componentStack);
}

PART D: the render() method

Should the return type be ReactNode ?

render(): React.ReactNode {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return(
        <ErrorPage
          message={"Something went wrong..."}
        />
      );
    }

    return this.props.children;
  }
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • Is this syntax: import type { Store } from "redux"; allowed? – captain-yossarian from Ukraine Sep 16 '20 at 09:20
  • @captain-yossarian yes, it seems to be working just fine. But the `import type` syntax was release in a more recent version of Typescript, not sure which one, but it won't work in some older versions. – cbdeveloper Sep 16 '20 at 09:22
  • You should pass redux store to and not directly to your Component – captain-yossarian from Ukraine Sep 16 '20 at 09:25
  • I'm doing that also. But not for the `ErrorBoundary`. But I guess I can access the store with `useStore()` from `react-redux` since the `ErrorBoundary` will be inside the ``. Thanks. **EDIT:** actually I can't use `useStore` inside a class component. Just found that out. Got that error message: _React Hook "useStore" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function._ – cbdeveloper Sep 16 '20 at 09:29
  • PART C: This member must have an 'override' modifier because it overrides a member in the base class 'Component, ErrorBoundaryState, any>'.ts(4114) PART D: This member must have an 'override' modifier because it overrides a member in the base class 'Component, ErrorBoundaryState, any>'.ts(4114) – Kmylo darkstar Feb 03 '22 at 01:19

2 Answers2

20

You can use the following code as an Error Boundary:

import React, { Component, ErrorInfo, ReactNode } from "react";

interface Props {
  children: ReactNode;
}

interface State {
  hasError: boolean;
}

class ErrorBoundary extends Component<Props, State> {
  public state: State = {
    hasError: false
  };

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

  public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Uncaught error:", error, errorInfo);
  }

  public render() {
    if (this.state.hasError) {
      return <h1>Sorry.. there was an error</h1>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;
Shahmir Jadoon
  • 450
  • 4
  • 6
4

You will get all your answers in the file index.d.ts from @types/react. If you are using an IDE like VSCode, you can Ctrl+click on a type to directly go to its definition.

Here is the precise answers to your questions, but before, let me advise you to prefer using react hooks instead of classes.


EDIT by OP: I never use class components, always prefer function and hooks, but from React docs on Error Boundaries:

Error boundaries work like a JavaScript catch {} block, but for components. Only class components can be error boundaries.


The lines I give are the one from index.d.ts in version 16.9.49.

Part A: yes, that's the way to do.

Part B: as you can see at line 658, error is of type any and the return type is a partial of the state or null.

Part C: at line 641, you will see that error is of type Error and return type is void.

Part D: at line 494, you can see that render should return a ReactNode

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
Stéphane Veyret
  • 1,791
  • 1
  • 8
  • 15
  • 1
    Thanks, man. I was just looking into the type definitions file right now. I forgot that those methods signatures would be inside the type definitions file. I decided to use `unknown` for the `error` parameter in `getDerivedStateFromError(error)`, instead of `any`. – cbdeveloper Sep 16 '20 at 09:36