Skip to content

Instantly share code, notes, and snippets.

@andywer
Last active January 8, 2025 04:42
Show Gist options
  • Select an option

  • Save andywer/800f3f25ce3698e8f8b5f1e79fed5c9c to your computer and use it in GitHub Desktop.

Select an option

Save andywer/800f3f25ce3698e8f8b5f1e79fed5c9c to your computer and use it in GitHub Desktop.
React - Functional error boundaries

React - Functional error boundaries

Thanks to React hooks you have now happily turned all your classes into functional components.

Wait, all your components? Not quite. There is one thing that can still only be implemented using classes: Error boundaries.

There is just no functional equivalent for componentDidCatch and deriveStateFromError yet.

Proposed solution

The proposed solution is greatly inspired by the new React.memo() API.

import Catch from "./functional-error-boundary"

type Props = {
  children: React.ReactNode
}

const MyErrorBoundary = Catch(function MyErrorBoundary(props: Props, error?: Error) {
  if (error) {
    return (
      <div className="error-screen">
        <h2>An error has occured</h2>
        <h4>{error.message}</h4>
      </div>
    )
  } else {
    return <React.Fragment>{props.children}</React.Fragment>
  }
})

API

type ErrorHandler = (error: Error, info: React.ErrorInfo) => void

Catch(component: React.ComponentType)

Catch(component: React.ComponentType, errorHandler: ErrorHandler)

Wraps the functional component component to make it an error boundary. The caught error is passed to the component function as a second parameter.

The optional second argument to Catch() (errorHandler) is a function that acts as componentDidCatch(). Use it to handle side effects like error logging.

Implementation

Find the code below.

Outlook

Would be great to see something like this integrated into the React.js core library:

import React from "react"

type Props = {
  children: React.ReactNode
}

const MyErrorBoundary = React.Catch(function MyErrorBoundary(props: Props, error?: Error) {
  if (error) {
    return (
      <div className="error-screen">
        <h2>An error has occured</h2>
        <h4>{error.message}</h4>
      </div>
    )
  } else {
    return <React.Fragment>{props.children}</React.Fragment>
  }
})
import React from "react"
type ErrorHandler = (error: Error, info: React.ErrorInfo) => void
type ErrorHandlingComponent<Props> = (props: Props, error?: Error) => React.ReactNode
type ErrorState = { error?: Error }
export default function Catch<Props extends {}>(
component: ErrorHandlingComponent<Props>,
errorHandler?: ErrorHandler
): React.ComponentType<Props> {
return class extends React.Component<Props, ErrorState> {
state: ErrorState = {
error: undefined
}
static getDerivedStateFromError(error: Error) {
return { error }
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
if (errorHandler) {
errorHandler(error, info)
}
}
render() {
return component(this.props, this.state.error)
}
}
}
@codinggirl

Copy link
Copy Markdown

Great work.

@MariuszKogut

Copy link
Copy Markdown

I like it, but it should be part of react. Maybe you can contribute? :-)

ghost commented May 14, 2020

Copy link
Copy Markdown

Thank you for writing this. It should definitely be contributed back to React.

@timeswind

Copy link
Copy Markdown

it seems like not work on function component using hooks

@andywer

andywer commented May 17, 2020

Copy link
Copy Markdown
Author

@timeswind Good point! You could try this 😉

(Sorry, didn't have time to test it yet)

import React from "react"

type ErrorHandler = (error: Error, info: React.ErrorInfo) => void
type ErrorHandlingComponent<Props> = (props: Props, error?: Error) => React.ReactNode

type ErrorState = { error?: Error }

export default function Catch<Props extends {}>(
  component: ErrorHandlingComponent<Props>,
  errorHandler?: ErrorHandler
): React.ComponentType<Props> {
  function Inner(props: { error?: Error, props: Props }) {
    return <React.Fragment>{component(props, error)}</React.Fragment>
  }

  return class extends React.Component<Props, ErrorState> {
    state: ErrorState = {
      error: undefined
    }
    
    static getDerivedStateFromError(error: Error) {
      return { error }
    }
    
    componentDidCatch(error: Error, info: React.ErrorInfo) {
      if (errorHandler) {
        errorHandler(error, info)
      }
    }
    
    render() {
      return <Inner error={this.state.error} props={this.props} />
    }
  }
}

@Stringsaeed

Copy link
Copy Markdown

compoonent(props, error) will raise error if your tried to use hooks, so it better to deal with it as JSX element

@DanyRupes

Copy link
Copy Markdown

Nice

@Morriz

Morriz commented Aug 4, 2020

Copy link
Copy Markdown

I am using hooks, but I get a type check error on the last snippet on L[13,25]:

Argument of type '{ error?: Error; props: Props; }' is not assignable to parameter of type 'Props'.
  '{ error?: Error; props: Props; }' is assignable to the constraint of type 'Props', but 'Props' could be instantiated with a different subtype of constraint '{}'.ts(2345)
...

So I fixed it by changing L13 to:

  function Inner({ props, error }: { error?: Error; props: Props }) {

@andywer

andywer commented Aug 4, 2020

Copy link
Copy Markdown
Author

@Morriz I think it must be return <React.Fragment>{component(props.props, props.error)}</React.Fragment>. Haven't tried yet, though.

@andreasjpunkt

Copy link
Copy Markdown

You have to rename the error boundary file to .tsx instead of .ts

@andywer

andywer commented Aug 5, 2020

Copy link
Copy Markdown
Author

@ajenkuhnabat Absolutely true. Renamed it!

@andywer

andywer commented Aug 5, 2020

Copy link
Copy Markdown
Author

Should I turn this little gist into a package? Seems to have finally gained a little bit of traction.

@asifsaho

asifsaho commented Aug 5, 2020

Copy link
Copy Markdown

Should I turn this little gist into a package? Seems to have finally gained a little bit of traction.

Yes please, I also need this. Although I couldn't make the above gists work yet.

ghost commented Aug 5, 2020

Copy link
Copy Markdown

Should I turn this little gist into a package? Seems to have finally gained a little bit of traction.

Have you already seen this - https://www.npmjs.com/package/react-error-boundary ?

Kent Dodds wrote an article on this - https://kentcdodds.com/blog/use-react-error-boundary-to-handle-errors-in-react

I really thought initially you had written this library because when you wrote this gist, I didn't find any such library.

The implementation also seems fairly close, thats why I was surprised.

@andywer

andywer commented Aug 6, 2020

Copy link
Copy Markdown
Author

@sriram-ethos No, I hadn't seen that before! I googled for something like this back then when I wrote this gist, but definitely didn't see that…

Yeah, I guess there's only so many ways to write such a component and stick to existing API patterns 🙂

ghost commented Aug 6, 2020

Copy link
Copy Markdown

Yeah, I guess there's only so many ways to write such a component and stick to existing API patterns 🙂

🙂

ghost commented Nov 10, 2020

Copy link
Copy Markdown

I really see no point in exporting a function when ultimately all I'll be doing will be cloaking a class instance inside a function. Why not just use the class directly? Wouldn't hurt to have a single class component rather than going through all this complexity, don't you think?

@VicJerUk

VicJerUk commented Sep 20, 2021

Copy link
Copy Markdown

I really see no point in exporting a function when ultimately all I'll be doing will be cloaking a class instance inside a function. Why not just use the class directly? Wouldn't hurt to have a single class component rather than going through all this complexity, don't you think?

Absolutely. I can't see the issue here with just using the class component for ErrorBoundary.

@manakupadhyay

Copy link
Copy Markdown

@andywer Could you please show an example of how we can use it?

@andywer

andywer commented May 14, 2022

Copy link
Copy Markdown
Author

@manakupadhyay It can be used as in the example at the top, under "Outlook", just that it's not part of React:

type Props = {
  children: React.ReactNode
}

const MyErrorBoundary = Catch(function MyErrorBoundary(props: Props, error?: Error) {
  if (error) {
    return (
      <div className="error-screen">
        <h2>An error has occured</h2>
        <h4>{error.message}</h4>
      </div>
    )
  } else {
    return <React.Fragment>{props.children}</React.Fragment>
  }
})

and then

function MyComponent() {
  return (
    <MyErrorBoundary>
      <ContentThatMightThrow>
    </MyErrorBoundary>
  );
}

@manakupadhyay

Copy link
Copy Markdown

@andywer Thanks a lot :)

@ABHISHEK-KEDAR-21

Copy link
Copy Markdown

Well I am not a fan of functional components and stick to class based components but Thanks anyway for writing this.

ghost commented Jul 28, 2023

Copy link
Copy Markdown

i tried it works great how but it should reset the state of error if click on something and it takes me to a new route how can i do that i tried to see where can i do it but couldnt find any appropriate place

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment