├── .gitattributes
├── .github
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── babel.config.cjs
├── eslint.config.mjs
├── other
├── MAINTAINING.md
├── USERS.md
└── manual-releases.md
├── package.json
├── pnpm-lock.yaml
├── src
├── ErrorBoundary.test.tsx
├── ErrorBoundary.ts
├── ErrorBoundaryContext.ts
├── assertErrorBoundaryContext.ts
├── env-conditions
│ ├── development.ts
│ └── production.ts
├── index.ts
├── types.ts
├── useErrorBoundary.test.tsx
├── useErrorBoundary.ts
├── withErrorBoundary.test.tsx
└── withErrorBoundary.ts
├── tsconfig.json
└── vitest.config.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
13 |
14 | - `react-error-boundary` version:
15 | - `node` version:
16 | - `npm` version:
17 |
18 | Relevant code or config
19 |
20 | ```js
21 |
22 | ```
23 |
24 | What you did:
25 |
26 | What happened:
27 |
28 |
29 |
30 | Reproduction repository:
31 |
32 |
36 |
37 | Problem description:
38 |
39 | Suggested solution:
40 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | **What**:
20 |
21 |
22 |
23 | **Why**:
24 |
25 |
26 |
27 | **How**:
28 |
29 |
30 |
31 | **Checklist**:
32 |
33 |
34 |
35 |
36 | - [ ] Documentation
37 | - [ ] Tests
38 | - [ ] Ready to be merged
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened]
6 |
7 | jobs:
8 | ci:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | command: ["pnpm lint", "pnpm typescript", "pnpm test"]
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: pnpm/action-setup@v4
16 | - uses: actions/setup-node@v4
17 | with:
18 | cache: "pnpm"
19 | cache-dependency-path: "pnpm-lock.yaml"
20 | node-version-file: ".nvmrc"
21 | - run: pnpm install --frozen-lockfile
22 | - run: pnpm prerelease
23 | - name: Run cis
24 | run: ${{ matrix.command }}
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
4 | .DS_Store
5 | .cache
6 | *.log
7 | .parcel-cache
8 | .pnp.*
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v20.9.0
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": false,
6 | "arrowParens": "always",
7 | "bracketSameLine": false,
8 | "bracketSpacing": true,
9 | "endOfLine": "lf",
10 | "jsxSingleQuote": false,
11 | "proseWrap": "preserve",
12 | "quoteProps": "as-needed"
13 | }
14 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | See the [releases page](../../releases).
4 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | me+coc@kentcdodds.com. All complaints will be reviewed and investigated promptly
64 | and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for being willing to contribute!
4 |
5 | **Working on your first Pull Request?** You can learn how from this _free_
6 | series [How to Contribute to an Open Source Project on GitHub][egghead]
7 |
8 | ## Project setup
9 |
10 | 1. Fork and clone the repo
11 | 2. Run `pnpm install` to install dependencies and run validation (We use pnpm so you need to follow [pnpm installation guide](https://pnpm.io/installation) first)
12 | 3. Create a branch for your PR with `git checkout -b pr/your-branch-name`
13 |
14 | > Tip: Keep your `master` branch pointing at the original repository and make
15 | > pull requests from branches on your fork. To do this, run:
16 | >
17 | > ```
18 | > git remote add upstream https://github.com/bvaughn/react-error-boundary
19 | > git fetch upstream
20 | > git branch --set-upstream-to=upstream/master master
21 | > ```
22 | >
23 | > This will add the original repository as a "remote" called "upstream," Then
24 | > fetch the git information from that remote, then set your local `master`
25 | > branch to use the upstream master branch whenever you run `git pull`. Then you
26 | > can make all of your pull request branches based on this `master` branch.
27 | > Whenever you want to update your version of `master`, do a regular `git pull`.
28 |
29 | ## Committing and Pushing changes
30 |
31 | Please make sure to run the tests before you commit your changes. You can run `pnpm test`. Make
32 | sure to include those changes (if they exist) in your commit.
33 |
34 | ## Help needed
35 |
36 | Please checkout the [open issues][issues]
37 |
38 | Also, please watch the repo and respond to questions/bug reports/feature
39 | requests! Thanks!
40 |
41 |
42 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
43 | [issues]: https://github.com/bvaughn/react-error-boundary/issues
44 |
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2020 Brian Vaughn
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-error-boundary
2 |
3 | Reusable React [error boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) component. Supports all React renderers (including React DOM and React Native).
4 |
5 | ### If you like this project, 🎉 [become a sponsor](https://github.com/sponsors/bvaughn/) or ☕ [buy me a coffee](http://givebrian.coffee/)
6 |
7 | ## Getting started
8 |
9 | ```sh
10 | # npm
11 | npm install react-error-boundary
12 |
13 | # pnpm
14 | pnpm add react-error-boundary
15 |
16 | # yarn
17 | yarn add react-error-boundary
18 | ```
19 |
20 | ## API
21 |
22 | ### `ErrorBoundary` component
23 | Wrap an `ErrorBoundary` component around other React components to "catch" errors and render a fallback UI. The component supports several ways to render a fallback (as shown below).
24 |
25 | > **Note** `ErrorBoundary` is a _client_ component. You can only pass props to it that are serializeable or use it in files that have a `"use client";` directive.
26 |
27 | #### `ErrorBoundary` with `fallback` prop
28 | The simplest way to render a default "something went wrong" type of error message.
29 | ```js
30 | "use client";
31 |
32 | import { ErrorBoundary } from "react-error-boundary";
33 |
34 | Something went wrong}>
35 |
36 |
37 | ```
38 | #### `ErrorBoundary` with `fallbackRender` prop
39 | ["Render prop"](https://react.dev/reference/react/Children#calling-a-render-prop-to-customize-rendering) function responsible for returning a fallback UI based on a thrown value.
40 | ```js
41 | "use client";
42 |
43 | import { ErrorBoundary } from "react-error-boundary";
44 |
45 | function fallbackRender({ error, resetErrorBoundary }) {
46 | // Call resetErrorBoundary() to reset the error boundary and retry the render.
47 |
48 | return (
49 |
50 |
Something went wrong:
51 |
{error.message}
52 |
53 | );
54 | }
55 |
56 | {
59 | // Reset the state of your app so the error doesn't happen again
60 | }}
61 | >
62 |
63 | ;
64 | ```
65 | #### `ErrorBoundary` with `FallbackComponent` prop
66 | React component responsible for returning a fallback UI based on a thrown value.
67 | ```js
68 | "use client";
69 |
70 | import { ErrorBoundary } from "react-error-boundary";
71 |
72 | function Fallback({ error, resetErrorBoundary }) {
73 | // Call resetErrorBoundary() to reset the error boundary and retry the render.
74 |
75 | return (
76 |
77 |
Something went wrong:
78 |
{error.message}
79 |
80 | );
81 | }
82 |
83 | {
86 | // Reset the state of your app so the error doesn't happen again
87 | }}
88 | >
89 |
90 | ;
91 | ```
92 |
93 | #### Logging errors with `onError`
94 |
95 | ```js
96 | "use client";
97 |
98 | import { ErrorBoundary } from "react-error-boundary";
99 |
100 | const logError = (error: Error, info: { componentStack: string }) => {
101 | // Do something with the error, e.g. log to an external API
102 | };
103 |
104 | const ui = (
105 |
106 |
107 |
108 | );
109 | ```
110 |
111 | ### `useErrorBoundary` hook
112 | Convenience hook for imperatively showing or dismissing error boundaries.
113 |
114 | #### Show the nearest error boundary from an event handler
115 |
116 | React only handles errors thrown during render or during component lifecycle methods (e.g. effects and did-mount/did-update). Errors thrown in event handlers, or after async code has run, will not be caught.
117 |
118 | This hook can be used to pass those errors to the nearest error boundary:
119 |
120 | ```js
121 | "use client";
122 |
123 | import { useErrorBoundary } from "react-error-boundary";
124 |
125 | function Example() {
126 | const { showBoundary } = useErrorBoundary();
127 |
128 | useEffect(() => {
129 | fetchGreeting(name).then(
130 | response => {
131 | // Set data in state and re-render
132 | },
133 | error => {
134 | // Show error boundary
135 | showBoundary(error);
136 | }
137 | );
138 | });
139 |
140 | // Render ...
141 | }
142 | ```
143 |
144 | #### Dismiss the nearest error boundary
145 | A fallback component can use this hook to request the nearest error boundary retry the render that originally failed.
146 |
147 | ```js
148 | "use client";
149 |
150 | import { useErrorBoundary } from "react-error-boundary";
151 |
152 | function ErrorFallback({ error }) {
153 | const { resetBoundary } = useErrorBoundary();
154 |
155 | return (
156 |
157 |
Something went wrong:
158 |
{error.message}
159 |
160 |
161 | );
162 | }
163 | ```
164 |
165 | ### `withErrorBoundary` HOC
166 | This package can also be used as a [higher-order component](https://legacy.reactjs.org/docs/higher-order-components.html) that accepts all of the same props as above:
167 |
168 | ```js
169 | "use client";
170 |
171 | import {withErrorBoundary} from 'react-error-boundary'
172 |
173 | const ComponentWithErrorBoundary = withErrorBoundary(ExampleComponent, {
174 | fallback:
Something went wrong
,
175 | onError(error, info) {
176 | // Do something with the error
177 | // E.g. log to an error logging client here
178 | },
179 | })
180 |
181 | // Can be rendered as
182 | ```
183 |
184 | ---
185 |
186 | # FAQ
187 | ## `ErrorBoundary` cannot be used as a JSX component
188 | This error can be caused by a version mismatch between [react](https://npmjs.com/package/react) and [@types/react](https://npmjs.com/package/@types/react). To fix this, ensure that both match exactly, e.g.:
189 |
190 | If using NPM:
191 | ```json
192 | {
193 | ...
194 | "overrides": {
195 | "@types/react": "17.0.60"
196 | },
197 | ...
198 | }
199 | ```
200 |
201 | If using Yarn:
202 | ```json
203 | {
204 | ...
205 | "resolutions": {
206 | "@types/react": "17.0.60"
207 | },
208 | ...
209 | }
210 | ```
211 |
212 | ---
213 |
214 | [This blog post](https://kentcdodds.com/blog/use-react-error-boundary-to-handle-errors-in-react) shows more examples of how this package can be used, although it was written for the [version 3 API](https://github.com/bvaughn/react-error-boundary/releases/tag/v3.1.4).
215 |
--------------------------------------------------------------------------------
/babel.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | "@babel/preset-typescript",
4 | [
5 | "@babel/preset-env",
6 | {
7 | targets: {
8 | safari: "12",
9 | },
10 | },
11 | ],
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import globals from "globals";
2 | import tseslint from "typescript-eslint";
3 | import reactPlugin from "eslint-plugin-react";
4 | import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
5 |
6 | export default tseslint.config(
7 | {
8 | ignores: ["dist"],
9 | },
10 | reactPlugin.configs.flat.recommended,
11 | tseslint.configs.recommended,
12 | {
13 | languageOptions: {
14 | globals: {
15 | ...globals.browser,
16 | ...globals.es2021,
17 | },
18 | parser: tseslint.parser,
19 | parserOptions: {
20 | ecmaVersion: "latest",
21 | sourceType: "module",
22 | },
23 | },
24 | plugins: {
25 | react: reactPlugin,
26 | "@typescript-eslint": tseslint.plugin,
27 | },
28 | rules: {
29 | "react/no-did-update-set-state": "off",
30 | "react/react-in-jsx-scope": "off",
31 | "react/prop-types": "off",
32 | "@typescript-eslint/no-explicit-any": "off",
33 | },
34 | },
35 | eslintPluginPrettierRecommended
36 | );
37 |
--------------------------------------------------------------------------------
/other/MAINTAINING.md:
--------------------------------------------------------------------------------
1 | # Maintaining
2 |
3 |
4 |
5 |
6 | **Table of Contents**
7 |
8 | - [Code of Conduct](#code-of-conduct)
9 | - [Issues](#issues)
10 | - [Pull Requests](#pull-requests)
11 | - [Release](#release)
12 | - [Thanks!](#thanks)
13 |
14 |
15 |
16 | This is documentation for maintainers of this project.
17 |
18 | ## Code of Conduct
19 |
20 | Please review, understand, and be an example of it. Violations of the code of
21 | conduct are taken seriously, even (especially) for maintainers.
22 |
23 | ## Issues
24 |
25 | We want to support and build the community. We do that best by helping people
26 | learn to solve their own problems. We have an issue template and hopefully most
27 | folks follow it. If it's not clear what the issue is, invite them to create a
28 | minimal reproduction of what they're trying to accomplish or the bug they think
29 | they've found.
30 |
31 | Once it's determined that a code change is necessary, point people to
32 | [makeapullrequest.com](https://makeapullrequest.com) and invite them to make a
33 | pull request. If they're the one who needs the feature, they're the one who can
34 | build it. If they need some hand holding and you have time to lend a hand,
35 | please do so. It's an investment into another human being, and an investment
36 | into a potential maintainer.
37 |
38 | Remember that this is open source, so the code is not yours, it's ours. If
39 | someone needs a change in the codebase, you don't have to make it happen
40 | yourself. Commit as much time to the project as you want/need to. Nobody can ask
41 | any more of you than that.
42 |
43 | ## Pull Requests
44 |
45 | As a maintainer, you're fine to make your branches on the main repo or on your
46 | own fork. Either way is fine.
47 |
48 | When we receive a pull request, a GitHub Action is kicked off automatically (see
49 | the `.github/workflows/validate.yml` for what runs in the Action). We avoid
50 | merging anything that breaks the GitHub Action.
51 |
52 | Please review PRs and focus on the code rather than the individual. You never
53 | know when this is someone's first ever PR and we want their experience to be as
54 | positive as possible, so be uplifting and constructive.
55 |
56 | When you merge the pull request, 99% of the time you should use the
57 | [Squash and merge](https://help.github.com/articles/merging-a-pull-request/)
58 | feature. This keeps our git history clean, but more importantly, this allows us
59 | to make any necessary changes to the commit message so we release what we want
60 | to release. See the next section on Releases for more about that.
61 |
62 | ## Release
63 |
64 | Our releases are automatic. They happen whenever code lands into `main`. A
65 | GitHub Action gets kicked off and if it's successful, a tool called
66 | [`semantic-release`](https://github.com/semantic-release/semantic-release) is
67 | used to automatically publish a new release to npm as well as a changelog to
68 | GitHub. It is only able to determine the version and whether a release is
69 | necessary by the git commit messages. With this in mind, **please brush up on
70 | [the commit message convention][commit] which drives our releases.**
71 |
72 | > One important note about this: Please make sure that commit messages do NOT
73 | > contain the words "BREAKING CHANGE" in them unless we want to push a major
74 | > version. I've been burned by this more than once where someone will include
75 | > "BREAKING CHANGE: None" and it will end up releasing a new major version. Not
76 | > a huge deal honestly, but kind of annoying...
77 |
78 | ## Thanks!
79 |
80 | Thank you so much for helping to maintain this project!
81 |
82 |
83 | [commit]: https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md
84 |
85 |
--------------------------------------------------------------------------------
/other/USERS.md:
--------------------------------------------------------------------------------
1 | # Users
2 |
3 | If you or your company uses this project, add your name to this list! Eventually
4 | we may have a website to showcase these (wanna build it!?)
5 |
6 | > No users have been added yet!
7 |
8 |
13 |
--------------------------------------------------------------------------------
/other/manual-releases.md:
--------------------------------------------------------------------------------
1 | # manual-releases
2 |
3 | This project has an automated release set up. So things are only released when
4 | there are useful changes in the code that justify a release. But sometimes
5 | things get messed up one way or another and we need to trigger the release
6 | ourselves. When this happens, simply bump the number below and commit that with
7 | the following commit message based on your needs:
8 |
9 | **Major**
10 |
11 | ```
12 | fix(release): manually release a major version
13 |
14 | There was an issue with a major release, so this manual-releases.md
15 | change is to release a new major version.
16 |
17 | Reference: #
18 |
19 | BREAKING CHANGE:
20 | ```
21 |
22 | **Minor**
23 |
24 | ```
25 | feat(release): manually release a minor version
26 |
27 | There was an issue with a minor release, so this manual-releases.md
28 | change is to release a new minor version.
29 |
30 | Reference: #
31 | ```
32 |
33 | **Patch**
34 |
35 | ```
36 | fix(release): manually release a patch version
37 |
38 | There was an issue with a patch release, so this manual-releases.md
39 | change is to release a new patch version.
40 |
41 | Reference: #
42 | ```
43 |
44 | The number of times we've had to do a manual release is: 0
45 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-error-boundary",
3 | "version": "6.0.0",
4 | "type": "module",
5 | "description": "Simple reusable React error boundary component",
6 | "author": "Brian Vaughn ",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/bvaughn/react-error-boundary"
11 | },
12 | "packageManager": "pnpm@9.6.0",
13 | "exports": {
14 | ".": {
15 | "types": "./dist/react-error-boundary.js",
16 | "development": "./dist/react-error-boundary.development.js",
17 | "default": "./dist/react-error-boundary.js"
18 | },
19 | "./package.json": "./package.json"
20 | },
21 | "imports": {
22 | "#is-development": {
23 | "development": "./src/env-conditions/development.ts",
24 | "default": "./src/env-conditions/production.ts"
25 | }
26 | },
27 | "types": "dist/react-error-boundary.d.ts",
28 | "files": [
29 | "dist"
30 | ],
31 | "sideEffects": false,
32 | "scripts": {
33 | "clear": "pnpm clear:builds & pnpm clear:node_modules",
34 | "clear:builds": "rimraf ./dist",
35 | "clear:node_modules": "rimraf ./node_modules",
36 | "prerelease": "preconstruct build",
37 | "lint": "eslint .",
38 | "lint:fix": "eslint . --fix",
39 | "test": "vitest --environment=jsdom --watch=false",
40 | "test:watch": "vitest --environment=jsdom --watch",
41 | "typescript": "tsc --noEmit",
42 | "typescript:watch": "tsc --noEmit --watch"
43 | },
44 | "dependencies": {
45 | "@babel/runtime": "^7.12.5"
46 | },
47 | "devDependencies": {
48 | "@babel/preset-env": "^7.22.5",
49 | "@babel/preset-typescript": "^7.21.5",
50 | "@preconstruct/cli": "^2.8.12",
51 | "@types/assert": "^1.5.10",
52 | "@types/react": "^18.3.17",
53 | "@types/react-dom": "^18",
54 | "assert": "^2.0.0",
55 | "eslint": "^9.13.0",
56 | "eslint-config-prettier": "^9.1.0",
57 | "eslint-plugin-import": "^2.25.2",
58 | "eslint-plugin-prettier": "^5.2.1",
59 | "eslint-plugin-react": "^7.37.2",
60 | "globals": "^15.11.0",
61 | "prettier": "^3.0.1",
62 | "react": "^18",
63 | "react-dom": "^18",
64 | "rimraf": "^6.0.1",
65 | "vitest": "^3.1.2",
66 | "typescript": "^5.8.3",
67 | "typescript-eslint": "^8.18.0"
68 | },
69 | "peerDependencies": {
70 | "react": ">=16.13.1"
71 | },
72 | "preconstruct": {
73 | "exports": {
74 | "importConditionDefaultExport": "default"
75 | },
76 | "___experimentalFlags_WILL_CHANGE_IN_PATCH": {
77 | "distInRoot": true,
78 | "importsConditions": true,
79 | "typeModule": true
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/ErrorBoundary.test.tsx:
--------------------------------------------------------------------------------
1 | import { describe, beforeEach, vi, it, expect, Mock } from "vitest";
2 | import assert from "assert";
3 | import { createRef, PropsWithChildren, ReactElement, RefObject } from "react";
4 | import { createRoot } from "react-dom/client";
5 | import { act } from "react-dom/test-utils";
6 | import { ErrorBoundary } from "./ErrorBoundary";
7 | import {
8 | ErrorBoundaryPropsWithComponent,
9 | ErrorBoundaryPropsWithFallback,
10 | ErrorBoundaryPropsWithRender,
11 | FallbackProps,
12 | } from "./types";
13 |
14 | describe("ErrorBoundary", () => {
15 | let container: HTMLDivElement;
16 | let root: ReturnType;
17 | let shouldThrow = true;
18 | let valueToThrow: any;
19 |
20 | beforeEach(() => {
21 | // @ts-expect-error This is a React internal
22 | global.IS_REACT_ACT_ENVIRONMENT = true;
23 |
24 | // Don't clutter the console with expected error text
25 | vi.spyOn(console, "error").mockImplementation(() => {
26 | // No-op
27 | });
28 |
29 | container = document.createElement("div");
30 | root = createRoot(container);
31 | shouldThrow = false;
32 | valueToThrow = new Error("💥💥💥");
33 | });
34 |
35 | function MaybeThrows({ children }: PropsWithChildren) {
36 | if (shouldThrow) {
37 | throw valueToThrow;
38 | }
39 | return children as any;
40 | }
41 |
42 | it("should render children", () => {
43 | const container = document.createElement("div");
44 | const root = createRoot(container);
45 | act(() => {
46 | root.render(
47 | Error}>
48 | Content
49 |
50 | );
51 | });
52 |
53 | expect(container.textContent).toBe("Content");
54 | });
55 |
56 | describe("fallback props", () => {
57 | let errorBoundaryRef: RefObject;
58 |
59 | beforeEach(() => {
60 | errorBoundaryRef = createRef();
61 | });
62 |
63 | function render(props: Omit) {
64 | act(() => {
65 | root.render(
66 |
67 | Content
68 |
69 | );
70 | });
71 | }
72 |
73 | it('should call "onError" prop if one is provided', () => {
74 | shouldThrow = true;
75 |
76 | const onError: Mock<(...args: any[]) => any> = vi.fn();
77 |
78 | render({ onError });
79 |
80 | expect(onError).toHaveBeenCalledTimes(1);
81 | expect(onError.mock.calls[0][0].message).toEqual("💥💥💥");
82 | });
83 |
84 | it('should call "onReset" when boundary reset via imperative API', () => {
85 | shouldThrow = true;
86 |
87 | const onReset: Mock<(...args: any[]) => any> = vi.fn();
88 |
89 | render({ onReset });
90 | expect(onReset).not.toHaveBeenCalled();
91 |
92 | act(() => errorBoundaryRef.current?.resetErrorBoundary("abc", 123));
93 |
94 | expect(onReset).toHaveBeenCalledTimes(1);
95 | });
96 |
97 | it('should call "onReset" when boundary reset via "resetKeys"', () => {
98 | shouldThrow = false;
99 |
100 | const onReset: Mock<(...args: any[]) => any> = vi.fn();
101 |
102 | render({ onReset, resetKeys: [1] });
103 | expect(onReset).not.toHaveBeenCalled();
104 |
105 | // It should not be called if the keys change without an error
106 | render({ onReset, resetKeys: [2] });
107 | expect(onReset).not.toHaveBeenCalled();
108 |
109 | shouldThrow = true;
110 |
111 | render({ onReset, resetKeys: [2] });
112 | expect(onReset).not.toHaveBeenCalled();
113 |
114 | shouldThrow = false;
115 |
116 | render({ onReset, resetKeys: [3] });
117 | expect(onReset).toHaveBeenCalledTimes(1);
118 | });
119 | });
120 |
121 | describe('"fallback" element', () => {
122 | function render(
123 | props: Omit = {}
124 | ) {
125 | act(() => {
126 | root.render(
127 | Error}>
128 | Content
129 |
130 | );
131 | });
132 | }
133 |
134 | it("should render fallback in the event of an error", () => {
135 | shouldThrow = true;
136 | render();
137 | expect(container.textContent).toBe("Error");
138 | });
139 |
140 | it("should re-render children if boundary is reset reset keys", () => {
141 | shouldThrow = true;
142 | render({ resetKeys: [1] });
143 |
144 | shouldThrow = false;
145 | expect(container.textContent).toBe("Error");
146 |
147 | render({ resetKeys: [2] });
148 | expect(container.textContent).toBe("Content");
149 | });
150 |
151 | it("should render a null fallback if specified", () => {
152 | shouldThrow = true;
153 | act(() => {
154 | root.render(
155 |
156 | Content
157 |
158 | );
159 | });
160 | expect(container.textContent).toBe("");
161 | });
162 | });
163 |
164 | describe('"FallbackComponent"', () => {
165 | let fallbackComponent: Mock<(props: FallbackProps) => ReactElement>;
166 | let lastRenderedError: any = null;
167 | let lastRenderedResetErrorBoundary:
168 | | FallbackProps["resetErrorBoundary"]
169 | | null = null;
170 |
171 | function render(
172 | props: Omit = {}
173 | ) {
174 | act(() => {
175 | root.render(
176 |
177 | Content
178 |
179 | );
180 | });
181 | }
182 |
183 | beforeEach(() => {
184 | lastRenderedError = null;
185 | lastRenderedResetErrorBoundary = null;
186 |
187 | fallbackComponent = vi.fn();
188 | fallbackComponent.mockImplementation(
189 | ({ error, resetErrorBoundary }: FallbackProps) => {
190 | lastRenderedError = error;
191 | lastRenderedResetErrorBoundary = resetErrorBoundary;
192 |
193 | return