Browse Source

Updated docs/recipes in response to a new GH question

This question showed there was a use-case that I wasn't covering fully enough before, so I've added a new section.
main
Brian Vaughn 7 years ago
parent
commit
813be17f1e
  1. 16
      content/blog/2018-02-07-update-on-async-rendering.md
  2. 2
      content/docs/strict-mode.md
  3. 29
      examples/update-on-async-rendering/fetching-external-data-after.js
  4. 22
      examples/update-on-async-rendering/fetching-external-data-before.js
  5. 55
      examples/update-on-async-rendering/updating-external-data-when-props-change-after.js
  6. 41
      examples/update-on-async-rendering/updating-external-data-when-props-change-before.js

16
content/blog/2018-02-07-update-on-async-rendering.md

@ -75,12 +75,12 @@ The simplest refactor for this type of component is to move state initialization
### Fetching external data
Here is an example of a component that uses `componentWillMount` and `componentWillUpdate` to fetch external data::
Here is an example of a component that uses `componentWillMount` to fetch external data:
`embed:update-on-async-rendering/fetching-external-data-before.js`
The above code is problematic for both server rendering (where the external data won't be used) and the upcoming async rendering mode (where the request might be initiated multiple times).
The recommended upgrade path for most use cases is to move data-fetching into `componentDidMount` and `componentDidUpdate`:
The recommended upgrade path for most use cases is to move data-fetching into `componentDidMount`:
`embed:update-on-async-rendering/fetching-external-data-after.js`
There is a common misconception that fetching in `componentWillMount` lets you avoid the first empty rendering state. In practice this was never true because React has always executed `render` immediately after `componentWillMount`. If the data is not available by the time `componentWillMount` fires, the first `render` will still show a loading state regardless of where you initiate the fetch. This is why moving the fetch to `componentDidMount` has no perceptible effect in the vast majority of cases.
@ -129,6 +129,18 @@ Sometimes people use `componentWillUpdate` out of a misplaced fear that by the t
Either way, it is unsafe to use `componentWillUpdate` for this purpose in async mode, because the external callback might get called multiple times for a single update. Instead, the `componentDidUpdate` lifecycle should be used since it is guaranteed to be invoked only once per update:
`embed:update-on-async-rendering/invoking-external-callbacks-after.js`
### Updating external data when `props` change
Here is an example of a component that fetches external data based on `props` values:
`embed:update-on-async-rendering/updating-external-data-when-props-change-before.js`
The recommended upgrade path for this component is to move data-updates into `componentDidUpdate`. You can also use the new `getDerivedStateFromProps` lifecycle to clear stale data before rendering the new props:
`embed:update-on-async-rendering/updating-external-data-when-props-change-after.js`
> **Note**
>
> If you're using an HTTP library that supports cancellation, like [axios](https://www.npmjs.com/package/axios), then it's simple to cancel an in-progress request when unmounting. For native Promises, you can use an approach like [the one shown here](https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6).
## Other scenarios
While we tried to cover the most common use cases in this post, we recognize that we might have missed some of them. If you are using `componentWillMount`, `componentWillUpdate`, or `componentWillReceiveProps` in ways that aren't covered by this blog post, and aren't sure how to migrate off these legacy lifecycles, please [file a new issue against our documentation](https://github.com/reactjs/reactjs.org/issues/new) with your code examples and as much background information as you can provide. We will update this document with new alternative patterns as they come up.

2
content/docs/strict-mode.md

@ -55,7 +55,7 @@ Conceptually, React does work in two phases:
* The **render** phase determines what changes need to be made to e.g. the DOM. During this phase, React calls `render` and then compares the result to the previous render.
* The **commit** phase is when React applies any changes. (In the case of React DOM, this is when React inserts, updates, and removes DOM nodes.) React also calls lifecycles like `componentDidMount` and `componentDidUpdate` during this phase.
The commit phase is usually very fast, but rendering can be slow. For this reason, the upcoming async mode (which is not enabled by default yet) breaks rendering into multiple pieces, pausing and resuming the work to avoid blocking the browser. This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).
The commit phase is usually very fast, but rendering can be slow. For this reason, the upcoming async mode (which is not enabled by default yet) breaks the rendering work into pieces, pausing and resuming the work to avoid blocking the browser. This means that React may invoke render phase lifecycles more than once before committing, or it may invoke them without committing at all (because of an error or a higher priority interruption).
Render phase lifecycles include the following class component methods:
* `constructor`

29
examples/update-on-async-rendering/fetching-external-data-after.js

@ -4,39 +4,24 @@ class ExampleComponent extends React.Component {
externalData: null,
};
// highlight-range{1-9}
// highlight-range{1-8}
componentDidMount() {
this._currentRequest = asyncLoadData(
this.props.id,
this._asyncRequest = asyncLoadData().then(
externalData => {
this._currentRequest = null;
this._asyncRequest = null;
this.setState({externalData});
}
);
}
// highlight-line
// highlight-range{1-11}
componentDidUpdate(prevProps, prevState) {
if (prevProps.id !== this.props.id) {
this._currentRequest = asyncLoadData(
this.props.id,
externalData => {
this._currentRequest = null;
this.setState({externalData});
}
);
}
}
// highlight-line
// highlight-range{1-5}
componentWillUnmount() {
if (this._currentRequest) {
this._currentRequest.cancel();
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.externalData === null) {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...

22
examples/update-on-async-rendering/fetching-external-data-before.js

@ -4,24 +4,24 @@ class ExampleComponent extends React.Component {
externalData: null,
};
// highlight-range{1-5}
// highlight-range{1-8}
componentWillMount() {
asyncLoadData(this.props.id).then(externalData =>
this.setState({externalData})
this._asyncRequest = asyncLoadData().then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
// highlight-line
// highlight-range{1-7}
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
asyncLoadData(this.props.id).then(externalData =>
this.setState({externalData})
);
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.externalData === null) {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...

55
examples/update-on-async-rendering/updating-external-data-when-props-change-after.js

@ -0,0 +1,55 @@
// After
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
// highlight-range{1-13}
static getDerivedStateFromProps(nextProps, prevState) {
// Store prevId in state so we can compare when props change.
// Clear out previously-loaded data (so we don't render stale stuff).
if (nextProps.id !== prevState.prevId) {
return {
externalData: null,
prevId: nextProps.id,
};
}
// No state update necessary
return null;
}
componentDidMount() {
this._loadAsyncData();
}
// highlight-range{1-5}
componentDidUpdate(prevProps, prevState) {
if (prevState.externalData === null) {
this._loadAsyncData();
}
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
_loadAsyncData() {
this._asyncRequest = asyncLoadData(this.props.id).then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
}

41
examples/update-on-async-rendering/updating-external-data-when-props-change-before.js

@ -0,0 +1,41 @@
// Before
class ExampleComponent extends React.Component {
state = {
externalData: null,
};
componentDidMount() {
this._loadAsyncData();
}
// highlight-range{1-6}
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
this.setState({externalData: null});
this._loadAsyncData();
}
}
componentWillUnmount() {
if (this._asyncRequest) {
this._asyncRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state ...
} else {
// Render real UI ...
}
}
_loadAsyncData() {
this._asyncRequest = asyncLoadData(this.props.id).then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
}
Loading…
Cancel
Save