Skip to content

Instantly share code, notes, and snippets.

@Svish
Created April 28, 2020 13:03
Show Gist options
  • Save Svish/2161e5b788e07f75a458839c93dc543e to your computer and use it in GitHub Desktop.
Save Svish/2161e5b788e07f75a458839c93dc543e to your computer and use it in GitHub Desktop.
ErrorBoundary listening to react-router history
import React from 'react';
import render, { RenderProp } from 'common/react/render';
import history from 'util/history';
import { UnregisterCallback } from 'history';
type ErrorInfo = { componentStack: string };
type CaughtError = Error & ErrorInfo;
interface State {
readonly error?: CaughtError;
}
export type Fallback = RenderProp<[CaughtError]>;
export type OnError = (error: CaughtError) => void;
export interface Props {
children: React.ReactNode;
fallback?: Fallback;
onError?: OnError;
}
export default class ErrorBoundary extends React.Component<Props, State> {
state: State = {};
unregister?: UnregisterCallback;
private handleHistory = (): void => {
this.hasError() && this.forceUpdate();
};
private static isError = (error?: Error): boolean => error != null;
private hasError = (): boolean => ErrorBoundary.isError(this.state.error);
componentDidMount(): void {
this.unregister = history.listen(this.handleHistory);
}
componentWillUnmount(): void {
this.unregister?.();
}
componentDidUpdate(_: Props, prevState: State): void {
if (this.hasError() && ErrorBoundary.isError(prevState.error))
this.setState({ error: undefined });
}
componentDidCatch(error: CaughtError, info?: ErrorInfo): void {
Object.assign(error, info);
this.props.onError?.(error);
this.setState({ error });
}
render(): React.ReactNode {
const { error } = this.state;
if (error == null) return this.props.children;
return render(this.props.fallback, error);
}
}
import { createBrowserHistory as createHistory } from 'history';
const history = createHistory();
export default history;
import { ReactNode, isValidElement } from 'react';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type AnyArg = any;
export type RenderFunction<A extends AnyArg[]> = (...args: A) => ReactNode;
export type RenderProp<A extends AnyArg[]> = ReactNode | RenderFunction<A>;
export function isRenderFunction<A extends AnyArg[]>(
subject: RenderProp<A>
): subject is RenderFunction<A> {
return !isValidElement(subject) && subject instanceof Function;
}
export default function render<A extends AnyArg[]>(
subject: RenderProp<A>,
...parameters: A
): ReactNode {
return isRenderFunction(subject) ? subject(...parameters) : subject ?? null;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment