ErrorBoundaries, Hooks & Class based components
Hooks are great, and used in conjunction with functional components it can make your frontend codebase very clean and efficient.
However, if you try to use one in an older, class based component (the type that usually read
class myClass extends React.Component
you will get an error which reads something like
Invalid hook call. Hooks can only be called inside of the body of a function component.
Usually you shouldn’t even hit this issue, as in 99% of cases anything you can do with a class based component you can do with a functional one and you will either be working with one or the other.
There are a few apis that can not be used in a functional component, and I hit this recently when trying to implement Error Boundaries
This relies on a static getDerivedStateFromError method which is part of the Component class and cannot be used in a functional component. There are plans for this pattern to be implemented into something that can be consumed by a functional component, but for now this needs a work around.
I opted to use a higher order component for this and simply pass the hook down as a prop.
my functional component (with hook) looks like this
function functionalComponent(Component) {
return function WrappedComponent(props) {
const customHook = useCustomHook();
return <Component {...props} customHook={customHook} showDialog={showDialog} />;
}
}
and my ErrorBoundry (or other class based component) can look like this
class ErrorBoundary extends Component {
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
//Error! perhaps do something with this.props.customHook which has been passed in
}
render() {
const showDialog = this.props.showDialog; //also passed in
if (showDialog) {
return <SystemError />;
}
return this.props.children;
}
}
ErrorBoundary.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
};
export default functionalComponent(ErrorBoundary);
And then in the main body of the app you can have something like
<ErrorBoundry>
<ChildrenHere />
</ErrorBoundry
This keeps the architecture of the app clean and maintainable, and when error boundaries get implemented as a hook on it’s own, the above can be easily refactored out and replaced with the fresh bits.