Browse Source

Added getSnapshotBeforeUpdate recipe

main
Brian Vaughn 7 years ago
parent
commit
b3bf0bd520
  1. 30
      content/blog/2018-03-15-update-on-async-rendering.md
  2. 19
      examples/update-on-async-rendering/new-lifecycles-overview.js
  3. 35
      examples/update-on-async-rendering/react-dom-properties-before-update-after.js
  4. 36
      examples/update-on-async-rendering/react-dom-properties-before-update-before.js

30
content/blog/2018-03-15-update-on-async-rendering.md

@ -29,27 +29,24 @@ However, if you'd like to start using the new component API (or if you're a main
---
Before we begin, here's a quick reminder of the lifecycle changes in version 16.3:
Before we begin, here's a quick overview of the lifecycle changes planned for version 16.3:
* We are adding the following lifecycle aliases: `UNSAFE_componentWillMount`, `UNSAFE_componentWillReceiveProps`, and `UNSAFE_componentWillUpdate`. (Both the old lifecycle names and the new aliases will be supported.)
* We are introducing a new, static lifecycle, `getDerivedStateFromProps`:
* We are introducing two new lifecycles, static `getDerivedStateFromProps` and `getSnapshotBeforeUpdate`:
```js
static getDerivedStateFromProps(
nextProps: Props,
prevState: State
): $Shape<State> | null
```
`embed:update-on-async-rendering/new-lifecycles-overview.js`
This new lifecycle is invoked after a component is instantiated and when it receives new props. It should return an object to update `state`, or `null` to indicate that the new `props` do not require any `state` updates.
The new static `getSnapshotBeforeUpdate` lifecycle is invoked after a component is instantiated as well as when it receives new props. It should return an object to update `state`, or `null` to indicate that the new `props` do not require any `state` updates.
---
The new `getSnapshotBeforeUpdate` lifecycle gets called right before mutations are made (e.g. before the DOM is updated). The return value for this lifecycle will be passed as the third parameter to `componentDidUpdate`.
Now let's take a look at some examples.
We'll look at examples of how both of these lifecycles can be used below.
> Note
>
> For brevity, the examples below are written using the experimental class properties transform, but the same migration strategies apply without it.
---
### Initializing state
This example shows a component with `setState` calls inside of `componentWillMount`:
@ -130,6 +127,17 @@ The recommended upgrade path for this component is to move data-updates into `co
>
> 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).
### Reading DOM properties before an update
Here is an example of a component that reads a property from the DOM before an update in order to maintain scroll position within a list:
`embed:update-on-async-rendering/react-dom-properties-before-update-before.js`
In the above example, `componentWillUpdate` is used to read the DOM property. However with async rendering, there may be delays between "render" phase lifecycles (like `componentWillUpdate` and `render`) and "commit" phase lifecycles (like `componentDidUpdate`). A user might continue scrolling during the delay, in which case the position value read from `componentWillUpdate` will be stale.
The solution to this problem is to use the new "commit" phase lifecycle, `getSnapshotBeforeUpdate`. This method gets called _immediately before_ mutations are made (e.g. before the DOM is updated). It can return a value for React to pass as a parameter to `componentDidUpdate`, which gets called _immediately after_ mutations.
`embed:update-on-async-rendering/react-dom-properties-before-update-after.js`
## 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.

19
examples/update-on-async-rendering/new-lifecycles-overview.js

@ -0,0 +1,19 @@
class Example extends React.Component<
Props,
State,
Snapshot
> {
static getDerivedStateFromProps(
nextProps: Props,
prevState: State
): $Shape<State> | null {
// ...
}
getSnapshotBeforeUpdate(
prevProps: Props,
prevState: State
): Snapshot {
// ...
}
}

35
examples/update-on-async-rendering/react-dom-properties-before-update-after.js

@ -0,0 +1,35 @@
class ScrollingList extends React.Component {
listRef = null;
// highlight-range{1-8}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the current height of the list so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
return this.listRef.scrollHeight;
}
return null;
}
// highlight-range{1-8}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
if (snapshot !== null) {
this.listRef.scrollTop +=
this.listRef.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.setListRef}>
{/* ...contents... */}
</div>
);
}
setListRef = ref => {
this.listRef = ref;
};
}

36
examples/update-on-async-rendering/react-dom-properties-before-update-before.js

@ -0,0 +1,36 @@
class ScrollingList extends React.Component {
listRef = null;
prevScrollHeight = null;
// highlight-range{1-7}
componentWillUpdate(nextProps, nextState) {
// Are we adding new items to the list?
// Capture the current height of the list so we can adjust scroll later.
if (this.props.list.length < nextProps.list.length) {
this.prevScrollHeight = this.listRef.scrollHeight;
}
}
// highlight-range{1-9}
componentDidUpdate(prevProps, prevState) {
// If prevScrollHeight is set, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
if (this.prevScrollHeight !== null) {
this.listRef.scrollTop +=
this.listRef.scrollHeight - this.prevScrollHeight;
this.prevScrollHeight = null;
}
}
render() {
return (
<div ref={this.setListRef}>
{/* ...contents... */}
</div>
);
}
setListRef = ref => {
this.listRef = ref;
};
}
Loading…
Cancel
Save