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
38 changes: 10 additions & 28 deletions content/docs/error-boundaries.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@ Error boundaries are React components that **catch JavaScript errors anywhere in
> * Server side rendering
> * Errors thrown in the error boundary itself (rather than its children)

A class component becomes an error boundary if it defines a new lifecycle method called `componentDidCatch(error, info)`:
A class component becomes an error boundary if it defines either (or both) of the lifecycle methods [`static getDerivedStateFromError()`](/docs/react-component.html#static-getderivedstatefromerror) or [`componentDidCatch()`](/docs/react-component.html#componentdidcatch). Use `static getDerivedStateFromError()` to render a fallback UI after an error has been thrown. Use `componentDidCatch()` to log error information.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok yeah this looks better


```js{7-12,15-18}
```js{7-10,12-15,18-21}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
Expand All @@ -43,7 +46,8 @@ class ErrorBoundary extends React.Component {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;

return this.props.children;
}
}
```
Expand All @@ -56,32 +60,10 @@ Then you can use it as a regular component:
</ErrorBoundary>
```

The `componentDidCatch()` method works like a JavaScript `catch {}` block, but for components. Only class components can be error boundaries. In practice, most of the time you’ll want to declare an error boundary component once and use it throughout your application.
Error boundaries work like a JavaScript `catch {}` block, but for components. Only class components can be error boundaries. In practice, most of the time you’ll want to declare an error boundary component once and use it throughout your application.

Note that **error boundaries only catch errors in the components below them in the tree**. An error boundary can’t catch an error within itself. If an error boundary fails trying to render the error message, the error will propagate to the closest error boundary above it. This, too, is similar to how catch {} block works in JavaScript.

### componentDidCatch Parameters

`error` is an error that has been thrown.

`info` is an object with `componentStack` key. The property has information about component stack during thrown error.

```js
//...
componentDidCatch(error, info) {

/* Example stack information:
in ComponentThatThrows (created by App)
in ErrorBoundary (created by App)
in div (created by App)
in App
*/
logComponentStackToMyService(info.componentStack);
}

//...
```

## Live Demo

Check out [this example of declaring and using an error boundary](https://codepen.io/gaearon/pen/wqvxGa?editors=0010) with [React 16](/blog/2017/09/26/react-v16.0.html).
Expand Down
101 changes: 96 additions & 5 deletions content/docs/reference-react-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ This method is called when a component is being removed from the DOM:

#### Error Handling

This method is called when there is an error during rendering, in a lifecycle method, or in the constructor of any child component.
These methods are called when there is an error during rendering, in a lifecycle method, or in the constructor of any child component.

- [`static getDerivedStateFromError()`](#getderivedstatefromerror)
- [`componentDidCatch()`](#componentdidcatch)

### Other APIs
Expand Down Expand Up @@ -290,6 +291,7 @@ This method doesn't have access to the component instance. If you'd like, you ca

Note that this method is fired on *every* render, regardless of the cause. This is in contrast to `UNSAFE_componentWillReceiveProps`, which only fires when the parent causes a re-render and not as a result of a local `setState`.

* * *

### `getSnapshotBeforeUpdate()`

Expand All @@ -311,21 +313,110 @@ In the above examples, it is important to read the `scrollHeight` property in `g

* * *

### Error boundaries

[Error boundaries](/docs/error-boundaries.html) are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

A class component becomes an error boundary if it defines either (or both) of the lifecycle methods `static getDerivedStateFromError()` or `componentDidCatch()`. Updating state from these lifecycles lets you capture an unhandled JavaScript error in the below tree and display a fallback UI.

Only use error boundaries for recovering from unexpected exceptions; **don't try to use them for control flow.**

For more details, see [*Error Handling in React 16*](/blog/2017/07/26/error-handling-in-react-16.html).

> Note
>
> Error boundaries only catch errors in the components **below** them in the tree. An error boundary can’t catch an error within itself.

### `static getDerivedStateFromError()`
```javascript
static getDerivedStateFromError(error)
```

This lifecycle is invoked after an error has been thrown by a descendant component.
It receives the error that was thrown as a parameter and should return a value to update state.

```js{7-10,13-16}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}
```

> Note
>
> `getDerivedStateFromError()` is called during the "render" phase, so side-effects are not permitted.
For those use cases, use `componentDidCatch()` instead.

* * *

### `componentDidCatch()`

```javascript
componentDidCatch(error, info)
```

[Error boundaries](/docs/error-boundaries.html) are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.
This lifecycle is invoked after an error has been thrown by a descendant component.
It receives two parameters:

A class component becomes an error boundary if it defines this lifecycle method. Calling `setState()` in it lets you capture an unhandled JavaScript error in the below tree and display a fallback UI. Only use error boundaries for recovering from unexpected exceptions; don't try to use them for control flow.
1. `error` - The error that was thrown.
2. `info` - An object with a `componentStack` key containing [information about which component threw the error](/docs/error-boundaries.html#component-stack-traces).

For more details, see [*Error Handling in React 16*](/blog/2017/07/26/error-handling-in-react-16.html).

`componentDidCatch()` is called during the "commit" phase, so side-effects are permitted.
It should be used for things like logging errors:

```js{12-19}
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

componentDidCatch(error, info) {
// Example "componentStack":
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logComponentStackToMyService(info.componentStack);
}

render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}
```

> Note
>
> Error boundaries only catch errors in the components **below** them in the tree. An error boundary can’t catch an error within itself.
> In the event of an error, you can render a fallback UI with `componentDidCatch()` by calling `setState`, but this will be deprecated in a future release.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this explanation earlier, and explain that defining componentDidCatch without also defining getDerivedStateFromError is also deprecated, and in the future you must define getDerivedStateFromError in order for the component to act as an error boundary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was on the fence about this. It felt a little odd to say this in the docs before we've actually officially deprecated the method.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but if we're going to have the two methods we should probably explain why

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do explain why, by saying one is used for rendering fallback UI and the other is used for e.g. logging. I was just a bit reluctant to say "and the other will also be deprecated" until we've actually done that.

It also feels a bit awkward to move the warning any earlier because then it would disconnect the code example. I don't feel strongly about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the other two edits. I'm not sure what to do with this one. If you have concrete wording or placement suggestions though I'm happy to make them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok maybe if you change the other sentence above then it will fix my concern

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are advantages of separating those 2 things? i understand the part about side effects being allowed in one and not in the other one, but componentDidCatch is working for both cases at the moment, so what's the reason behind separating them? non of them should really be called often (they are last resort kind of thing) so it raises a question if they are both needed as they will for sure be confusing for newcomers

Copy link
Contributor Author

@bvaughn bvaughn Oct 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great question! 😄Hopefully documentation will be sufficient to avoid confusion for newcomers, but it's a valid concern.

My understanding of this is that there are a couple of motivating factors:

It works with server-side rendering. componentDidCatch is a commit phase lifecycle, but there is no commit phase on the server. getDerivedStateFromError is a render phase lifecycle, and so it can be used to enable error handling on the server.

Render phase recovery is safer. The story for error recovery via componentDidCatch is a little janky, since it relies on an intermediate commit of "null" for everything below the component that errored. This might result in subsequent errors inside of any components higher up in the tree that implement componentDidMount or componentDidUpdate and just assume that their refs will be non-null (because they always are in the non-error case).

It's more optimal for async rendering. Because state-updates from commit phase lifecycles are always synchronous, and because componentDidCatch is called during the commit phase– using componentDidCatch for error recovery is not optimal because it forces the fallback UI to always render synchronously. (This is admittedly not a huge concern, since error recovery should be an edge case.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ofc dont judge the chosen APIs too harshly as Im not competent enough to do that, not knowing all intricacies of the implementation. Just commenting from the user's perspective. And thanks for the explanation!

Just want to comment a little bit on the given arguments

  • It works with server-side rendering - this is appealing
  • Render phase recovery is safer. - seems like implementation detail that could be handled differently, but i understand it would probably make the implementation more clunky as it would have to be special cased commit hook
  • It's more optimal for async rendering. - not the strongest argument (as you have mentioned - those should be edge cases and thus being optimal for rendering shouldnt matter as much)

> Use `static getDerivedStateFromError()` to handle fallback rendering instead.

* * *

Expand Down