Skip to content

Instantly share code, notes, and snippets.

@LeeCheneler
Last active June 3, 2020 09:18
Show Gist options
  • Save LeeCheneler/4b35207efb15521932601f41d2210e5d to your computer and use it in GitHub Desktop.
Save LeeCheneler/4b35207efb15521932601f41d2210e5d to your computer and use it in GitHub Desktop.
Reporting error boundary with fallback
import React from "react";
import { ErrorBoundary } from "./error-boundary";
export const App = () => {
return (
<React.StrictMode>
<ErrorBoundary report={console.error}>
<h1>Example</h1>
</ErrorBoundary>
</React.StrictMode>
);
};
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ErrorBoundaryFallback } from "./error-boundary-fallback";
describe("ErrorBoundaryFallback", () => {
it("should display error details", async () => {
const error = new Error();
render(<ErrorBoundaryFallback error={error} clearError={jest.fn()} />);
expect(await screen.findByText(error.toString())).toBeInTheDocument();
});
it("should clear error when 'Try again' clicked", async () => {
const error = new Error();
const clearErrorMock = jest.fn();
render(<ErrorBoundaryFallback error={error} clearError={clearErrorMock} />);
userEvent.click(await screen.findByText("Try again"));
expect(clearErrorMock).toHaveBeenCalledTimes(1);
});
});
import React from "react";
import { ErrorBoundaryFallbackProps } from "./error-boundary";
export const ErrorBoundaryFallback = (props: ErrorBoundaryFallbackProps) => {
return (
<>
<div>
<button onClick={props.clearError}>Try again</button>
</div>
<pre>{props.error.toString()}</pre>
</>
);
};
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ErrorBoundary, ErrorBoundaryFallbackProps } from "../error-boundary";
describe("ErrorBoundary", () => {
const error = new Error();
const ErrorBoundaryTestHarness = () => {
throw error;
};
const CustomErrorBoundaryFallback = (props: ErrorBoundaryFallbackProps) => {
return (
<span data-testid="custom-error-boundary-fallback">
{props.error.toString()}
</span>
);
};
beforeEach(() => {
global.console.error = jest.fn();
});
afterEach(() => {
(global.console.error as jest.Mock).mockRestore();
});
it("should render children when no error", async () => {
render(
<ErrorBoundary report={jest.fn()}>
<span data-testid="child" />
</ErrorBoundary>
);
expect(await screen.findByTestId("child")).toBeInTheDocument();
});
it("should render default fallback and report when error occurs", async () => {
const mockReport = jest.fn();
render(
<ErrorBoundary report={mockReport}>
<ErrorBoundaryTestHarness />
</ErrorBoundary>
);
expect(await screen.findByText(error.toString())).toBeInTheDocument();
expect(mockReport).toHaveBeenCalledTimes(1);
expect(mockReport).toHaveBeenCalledWith(error, {
componentStack: `
in ErrorBoundaryTestHarness
in ErrorBoundary`,
});
});
it("should render provided fallback", async () => {
render(
<ErrorBoundary report={jest.fn()} fallback={CustomErrorBoundaryFallback}>
<ErrorBoundaryTestHarness />
</ErrorBoundary>
);
expect(
await screen.findByTestId("custom-error-boundary-fallback")
).toBeInTheDocument();
});
it("should clear the error and rerender children when clearError is called", async () => {
const mockReport = jest.fn();
render(
<ErrorBoundary report={mockReport}>
<ErrorBoundaryTestHarness />
</ErrorBoundary>
);
expect(mockReport).toHaveBeenCalledTimes(1);
userEvent.click(await screen.findByText("Try again"));
expect(mockReport).toHaveBeenCalledTimes(2);
});
});
import React from "react";
import { ErrorBoundaryFallback } from "./error-boundary-fallback";
export interface ErrorBoundaryFallbackProps {
clearError: () => void;
error: Error;
}
export interface ErrorBoundaryProps {
children: React.ReactNode;
fallback: React.FC<ErrorBoundaryFallbackProps>;
report: (error: Error, errorInfo: React.ErrorInfo) => void | Promise<void>;
}
export interface ErrorBoundaryState {
error: Error | null;
}
export class ErrorBoundary extends React.Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
static defaultProps = {
fallback: ErrorBoundaryFallback,
};
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
error: null,
};
}
static getDerivedStateFromError = (error: Error) => {
return { error };
};
componentDidCatch = (error: Error, errorInfo: React.ErrorInfo) => {
this.props.report(error, errorInfo);
};
clearError = () => {
this.setState({
error: null,
});
};
render = () => {
const { error } = this.state;
if (error === null) return this.props.children;
return this.props.fallback({
error,
clearError: this.clearError,
});
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment