Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ None
<tr>
<td>onError</td>
<td><p>Optional callback to enable e.g. logging error information to a server.
@param error Error that was thrown
@param error Value that was thrown; typically an instance of <code>Error</code>
@param info React &quot;component stack&quot; identifying where the error was thrown</p>
</td>
</tr>
Expand Down
17 changes: 9 additions & 8 deletions lib/components/ErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
FallbackProps,
OnErrorCallback,
} from "../types";
import { getErrorMessage } from "../utils/getErrorMessage";

describe("ErrorBoundary", () => {
let container: HTMLDivElement;
Expand Down Expand Up @@ -84,7 +85,7 @@ describe("ErrorBoundary", () => {
render({ onError });

expect(onError).toHaveBeenCalledTimes(1);
expect(onError.mock.calls[0][0].message).toEqual("💥💥💥");
expect(getErrorMessage(onError.mock.calls[0][0])).toEqual("💥💥💥");
});

it('should call "onReset" when boundary reset via imperative API', () => {
Expand Down Expand Up @@ -169,7 +170,7 @@ describe("ErrorBoundary", () => {

describe('"FallbackComponent"', () => {
let fallbackComponent: Mock<(props: FallbackProps) => ReactElement>;
let lastRenderedError: Error | null = null;
let lastRenderedError: unknown | null = null;
let lastRenderedResetErrorBoundary:
| FallbackProps["resetErrorBoundary"]
| null = null;
Expand Down Expand Up @@ -204,7 +205,7 @@ describe("ErrorBoundary", () => {
it("should render fallback in the event of an error", () => {
shouldThrow = true;
render();
expect(lastRenderedError?.message).toBe("💥💥💥");
expect(getErrorMessage(lastRenderedError)).toBe("💥💥💥");
expect(container.textContent).toBe("FallbackComponent");
});

Expand Down Expand Up @@ -235,7 +236,7 @@ describe("ErrorBoundary", () => {
});

describe('"fallbackRender" render prop', () => {
let lastRenderedError: Error | null = null;
let lastRenderedError: unknown | null = null;
let lastRenderedResetErrorBoundary:
| FallbackProps["resetErrorBoundary"]
| null = null;
Expand Down Expand Up @@ -271,15 +272,15 @@ describe("ErrorBoundary", () => {
it("should render fallback in the event of an error", () => {
shouldThrow = true;
render();
expect(lastRenderedError?.message).toBe("💥💥💥");
expect(getErrorMessage(lastRenderedError)).toBe("💥💥💥");
expect(fallbackRender).toHaveBeenCalled();
expect(container.textContent).toBe("fallbackRender");
});

it("should re-render children if boundary is reset via prop", () => {
shouldThrow = true;
render();
expect(lastRenderedError?.message).toBe("💥💥💥");
expect(getErrorMessage(lastRenderedError)).toBe("💥💥💥");
expect(fallbackRender).toHaveBeenCalled();
expect(container.textContent).toBe("fallbackRender");

Expand All @@ -295,7 +296,7 @@ describe("ErrorBoundary", () => {
it("should re-render children if boundary is reset reset keys", () => {
shouldThrow = true;
render({ resetKeys: [1] });
expect(lastRenderedError?.message).toBe("💥💥💥");
expect(getErrorMessage(lastRenderedError)).toBe("💥💥💥");
expect(fallbackRender).toHaveBeenCalled();
expect(container.textContent).toBe("fallbackRender");

Expand All @@ -306,7 +307,7 @@ describe("ErrorBoundary", () => {
});

describe("thrown values", () => {
let lastRenderedError: Error | null = null;
let lastRenderedError: unknown | null = null;
let fallbackRender: (props: FallbackProps) => ReactElement;
let onError: Mock<(...args: unknown[]) => unknown>;

Expand Down
4 changes: 2 additions & 2 deletions lib/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const isDevelopment = import.meta.env.DEV;
type ErrorBoundaryState =
| {
didCatch: true;
error: Error;
error: unknown;
}
| {
didCatch: false;
Expand Down Expand Up @@ -63,7 +63,7 @@ export class ErrorBoundary extends Component<
}
}

componentDidCatch(error: Error, info: ErrorInfo) {
componentDidCatch(error: unknown, info: ErrorInfo) {
this.props.onError?.(error, info);
}

Expand Down
2 changes: 1 addition & 1 deletion lib/context/ErrorBoundaryContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createContext } from "react";

export type ErrorBoundaryContextType = {
didCatch: boolean;
error: Error | null;
error: unknown | null;
resetErrorBoundary: (...args: unknown[]) => void;
};

Expand Down
5 changes: 4 additions & 1 deletion lib/hooks/useErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ErrorBoundary } from "../components/ErrorBoundary";
import { assert } from "../utils/assert";
import { getErrorMessage } from "../utils/getErrorMessage";
import { useErrorBoundary, type UseErrorBoundaryApi } from "./useErrorBoundary";

describe("useErrorBoundary", () => {
Expand Down Expand Up @@ -41,7 +42,9 @@ describe("useErrorBoundary", () => {

render(
<ErrorBoundary
fallbackRender={({ error }) => <div>Fallback: {error.message}</div>}
fallbackRender={({ error }) => (
<div>Fallback: {getErrorMessage(error)}</div>
)}
>
<Child />
</ErrorBoundary>,
Expand Down
12 changes: 6 additions & 6 deletions lib/hooks/useErrorBoundary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { ErrorBoundaryContext } from "../context/ErrorBoundaryContext";
import { assertErrorBoundaryContext } from "../utils/assertErrorBoundaryContext";

type UseErrorBoundaryState =
| { error: Error; hasError: true }
| { error: unknown; hasError: true }
| { error: null; hasError: false };

export type UseErrorBoundaryApi = {
error: Error | null;
error: unknown | null;
resetBoundary: () => void;
showBoundary: (error: Error) => void;
showBoundary: (error: unknown) => void;
};

/**
Expand All @@ -21,7 +21,7 @@ export function useErrorBoundary(): {
/**
* The currently visible `Error` (if one has been thrown).
*/
error: Error | null;
error: unknown | null;

/**
* Method to reset and retry the nearest active error boundary (if one is active).
Expand All @@ -35,7 +35,7 @@ export function useErrorBoundary(): {
* Errors thrown in event handlers, or after async code has run, will not be caught.
* This method is a way to imperatively trigger an error boundary during these phases.
*/
showBoundary: (error: Error) => void;
showBoundary: (error: unknown) => void;
} {
const context = useContext(ErrorBoundaryContext);

Expand All @@ -55,7 +55,7 @@ export function useErrorBoundary(): {
resetErrorBoundary();
setState({ error: null, hasError: false });
},
showBoundary: (error: Error) =>
showBoundary: (error: unknown) =>
setState({
error,
hasError: true,
Expand Down
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export { ErrorBoundary } from "./components/ErrorBoundary";
export { ErrorBoundaryContext } from "./context/ErrorBoundaryContext";
export { useErrorBoundary } from "./hooks/useErrorBoundary";
export { getErrorMessage } from "./utils/getErrorMessage";
export { withErrorBoundary } from "./utils/withErrorBoundary";

export type { ErrorBoundaryContextType } from "./context/ErrorBoundaryContext";
Expand Down
8 changes: 4 additions & 4 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,20 @@ import type {
} from "react";

export type FallbackProps = {
error: Error;
error: unknown;
resetErrorBoundary: (...args: unknown[]) => void;
};

export type OnErrorCallback = (error: Error, info: ErrorInfo) => void;
export type OnErrorCallback = (error: unknown, info: ErrorInfo) => void;

type ErrorBoundarySharedProps = PropsWithChildren<{
/**
* Optional callback to enable e.g. logging error information to a server.
*
* @param error Error that was thrown
* @param error Value that was thrown; typically an instance of `Error`
* @param info React "component stack" identifying where the error was thrown
*/
onError?: (error: Error, info: ErrorInfo) => void;
onError?: (error: unknown, info: ErrorInfo) => void;

/**
* Optional callback to to be notified when an error boundary is "reset" so React can retry the failed render.
Expand Down
17 changes: 17 additions & 0 deletions lib/utils/getErrorMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function getErrorMessage(thrown: unknown): string | undefined {
switch (typeof thrown) {
case "object": {
if (
thrown !== null &&
"message" in thrown &&
typeof thrown.message === "string"
) {
return thrown.message;
}
break;
}
case "string": {
return thrown;
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-error-boundary",
"version": "6.0.3",
"version": "6.0.4",
"type": "module",
"description": "Simple reusable React error boundary component",
"author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
Expand Down
4 changes: 2 additions & 2 deletions public/generated/docs/ErrorBoundary.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
"onError": {
"description": [
{
"content": "<p>Optional callback to enable e.g. logging error information to a server.</p>\n<ul>\n<li><code>error</code> Error that was thrown</li>\n<li><code>info</code> React &quot;component stack&quot; identifying where the error was thrown</li>\n</ul>\n"
"content": "<p>Optional callback to enable e.g. logging error information to a server.</p>\n<ul>\n<li><code>error</code> Value that was thrown; typically an instance of <code>Error</code></li>\n<li><code>info</code> React &quot;component stack&quot; identifying where the error was thrown</li>\n</ul>\n"
}
],
"html": "<div><span class=\"tok-variableName\"><span class=\"tok-propertyName\">onError</span></span><span class=\"tok-operator\">?</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-punctuation\">(</span><span class=\"tok-variableName tok-definition\">error</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">Error</span><span class=\"tok-punctuation\">,</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">info</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">ErrorInfo</span><span class=\"tok-punctuation\">)</span><span class=\"\"> </span><span class=\"tok-punctuation\">=&#62;</span><span class=\"\"> </span><span class=\"tok-keyword\">void</span></div>",
"html": "<div><span class=\"tok-variableName\"><span class=\"tok-propertyName\">onError</span></span><span class=\"tok-operator\">?</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-punctuation\">(</span><span class=\"tok-variableName tok-definition\">error</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">unknown</span><span class=\"tok-punctuation\">,</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">info</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">ErrorInfo</span><span class=\"tok-punctuation\">)</span><span class=\"\"> </span><span class=\"tok-punctuation\">=&#62;</span><span class=\"\"> </span><span class=\"tok-keyword\">void</span></div>",
"name": "onError",
"required": false
},
Expand Down
2 changes: 1 addition & 1 deletion public/generated/examples/ErrorLogging.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"html": "<div><span class=\"tok-keyword\">import</span><span class=\"\"> </span><span class=\"tok-keyword\">type</span><span class=\"\"> </span><span class=\"tok-punctuation\">{</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">ErrorInfo</span><span class=\"\"> </span><span class=\"tok-punctuation\">}</span><span class=\"\"> </span><span class=\"tok-keyword\">from</span><span class=\"\"> </span><span class=\"tok-string\">\"react\"</span><span class=\"tok-punctuation\">;</span><span class=\"\"></span></div>\n<div><span class=\"\"></span><span class=\"tok-keyword\">import</span><span class=\"\"> </span><span class=\"tok-punctuation\">{</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">ErrorBoundary</span><span class=\"\"> </span><span class=\"tok-punctuation\">}</span><span class=\"\"> </span><span class=\"tok-keyword\">from</span><span class=\"\"> </span><span class=\"tok-string\">\"react-error-boundary\"</span><span class=\"tok-punctuation\">;</span><span class=\"\"></span></div>\n<div>&nbsp;</div>\n<div><span class=\"\"></span><span class=\"tok-keyword\">function</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">logError</span><span class=\"tok-punctuation\">(</span><span class=\"tok-variableName tok-definition\">error</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">Error</span><span class=\"tok-punctuation\">,</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">info</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">ErrorInfo</span><span class=\"tok-punctuation\">)</span><span class=\"\"> </span><span class=\"tok-punctuation\">{</span><span class=\"\"></span></div>\n<div><span class=\"\"> </span><span class=\"tok-comment\">// Do something with the error, e.g. log to an external API</span><span class=\"\"></span></div>\n<div><span class=\"\"></span><span class=\"tok-punctuation\">}</span><span class=\"\"></span></div>\n<div>&nbsp;</div>\n<div><span class=\"\"></span><span class=\"tok-punctuation\">&#60;</span><span class=\"tok-typeName\">ErrorBoundary</span><span class=\"\"> </span><span class=\"tok-propertyName\">FallbackComponent</span><span class=\"tok-operator\">=</span><span class=\"tok-punctuation\">{</span><span class=\"tok-variableName\">ErrorFallback</span><span class=\"tok-punctuation\">}</span><span class=\"\"> </span><span class=\"tok-propertyName\">onError</span><span class=\"tok-operator\">=</span><span class=\"tok-punctuation\">{</span><span class=\"tok-variableName\">logError</span><span class=\"tok-punctuation\">}</span><span class=\"tok-punctuation\">&#62;</span><span class=\"\"></span></div>\n<div><span class=\"\"> </span><span class=\"tok-punctuation\">&#60;</span><span class=\"tok-typeName\">YourApplication</span><span class=\"\"> </span><span class=\"tok-punctuation\">/&#62;</span><span class=\"\"></span></div>\n<div><span class=\"\"></span><span class=\"tok-punctuation\">&#60;/</span><span class=\"tok-typeName\">ErrorBoundary</span><span class=\"tok-punctuation\">&#62;</span><span class=\"tok-punctuation\">;</span></div>"
"html": "<div><span class=\"tok-keyword\">import</span><span class=\"\"> </span><span class=\"tok-keyword\">type</span><span class=\"\"> </span><span class=\"tok-punctuation\">{</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">ErrorInfo</span><span class=\"\"> </span><span class=\"tok-punctuation\">}</span><span class=\"\"> </span><span class=\"tok-keyword\">from</span><span class=\"\"> </span><span class=\"tok-string\">\"react\"</span><span class=\"tok-punctuation\">;</span><span class=\"\"></span></div>\n<div><span class=\"\"></span><span class=\"tok-keyword\">import</span><span class=\"\"> </span><span class=\"tok-punctuation\">{</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">ErrorBoundary</span><span class=\"\"> </span><span class=\"tok-punctuation\">}</span><span class=\"\"> </span><span class=\"tok-keyword\">from</span><span class=\"\"> </span><span class=\"tok-string\">\"react-error-boundary\"</span><span class=\"tok-punctuation\">;</span><span class=\"\"></span></div>\n<div>&nbsp;</div>\n<div><span class=\"\"></span><span class=\"tok-keyword\">function</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">logError</span><span class=\"tok-punctuation\">(</span><span class=\"tok-variableName tok-definition\">error</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">unknown</span><span class=\"tok-punctuation\">,</span><span class=\"\"> </span><span class=\"tok-variableName tok-definition\">info</span><span class=\"tok-punctuation\">:</span><span class=\"\"> </span><span class=\"tok-typeName\">ErrorInfo</span><span class=\"tok-punctuation\">)</span><span class=\"\"> </span><span class=\"tok-punctuation\">{</span><span class=\"\"></span></div>\n<div><span class=\"\"> </span><span class=\"tok-comment\">// Do something with the error, e.g. log to an external API</span><span class=\"\"></span></div>\n<div><span class=\"\"></span><span class=\"tok-punctuation\">}</span><span class=\"\"></span></div>\n<div>&nbsp;</div>\n<div><span class=\"\"></span><span class=\"tok-punctuation\">&#60;</span><span class=\"tok-typeName\">ErrorBoundary</span><span class=\"\"> </span><span class=\"tok-propertyName\">FallbackComponent</span><span class=\"tok-operator\">=</span><span class=\"tok-punctuation\">{</span><span class=\"tok-variableName\">ErrorFallback</span><span class=\"tok-punctuation\">}</span><span class=\"\"> </span><span class=\"tok-propertyName\">onError</span><span class=\"tok-operator\">=</span><span class=\"tok-punctuation\">{</span><span class=\"tok-variableName\">logError</span><span class=\"tok-punctuation\">}</span><span class=\"tok-punctuation\">&#62;</span><span class=\"\"></span></div>\n<div><span class=\"\"> </span><span class=\"tok-punctuation\">&#60;</span><span class=\"tok-typeName\">YourApplication</span><span class=\"\"> </span><span class=\"tok-punctuation\">/&#62;</span><span class=\"\"></span></div>\n<div><span class=\"\"></span><span class=\"tok-punctuation\">&#60;/</span><span class=\"tok-typeName\">ErrorBoundary</span><span class=\"tok-punctuation\">&#62;</span><span class=\"tok-punctuation\">;</span></div>"
}
Loading