| null {
+ // ...
+ }
+
+ getSnapshotBeforeUpdate(
+ prevProps: Props,
+ prevState: State
+ ): Snapshot {
+ // ...
+ }
+}
diff --git a/examples/update-on-async-rendering/react-dom-properties-before-update-after.js b/examples/update-on-async-rendering/react-dom-properties-before-update-after.js
new file mode 100644
index 00000000000..2fc188aa385
--- /dev/null
+++ b/examples/update-on-async-rendering/react-dom-properties-before-update-after.js
@@ -0,0 +1,36 @@
+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.
+ // (snapshot here is the value returned from getSnapshotBeforeUpdate)
+ if (snapshot !== null) {
+ this.listRef.scrollTop +=
+ this.listRef.scrollHeight - snapshot;
+ }
+ }
+
+ render() {
+ return (
+
+ {/* ...contents... */}
+
+ );
+ }
+
+ setListRef = ref => {
+ this.listRef = ref;
+ };
+}
diff --git a/examples/update-on-async-rendering/react-dom-properties-before-update-before.js b/examples/update-on-async-rendering/react-dom-properties-before-update-before.js
new file mode 100644
index 00000000000..ad26b4e818d
--- /dev/null
+++ b/examples/update-on-async-rendering/react-dom-properties-before-update-before.js
@@ -0,0 +1,37 @@
+class ScrollingList extends React.Component {
+ listRef = null;
+ previousScrollHeight = 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.previousScrollHeight = this.listRef.scrollHeight;
+ }
+ }
+
+ // highlight-range{1-10}
+ componentDidUpdate(prevProps, prevState) {
+ // If previousScrollHeight 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.previousScrollHeight !== null) {
+ this.listRef.scrollTop +=
+ this.listRef.scrollHeight -
+ this.previousScrollHeight;
+ this.previousScrollHeight = null;
+ }
+ }
+
+ render() {
+ return (
+
+ {/* ...contents... */}
+
+ );
+ }
+
+ setListRef = ref => {
+ this.listRef = ref;
+ };
+}
diff --git a/examples/update-on-async-rendering/updating-external-data-when-props-change-after.js b/examples/update-on-async-rendering/updating-external-data-when-props-change-after.js
new file mode 100644
index 00000000000..014a3c6194a
--- /dev/null
+++ b/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});
+ }
+ );
+ }
+}
diff --git a/examples/update-on-async-rendering/updating-external-data-when-props-change-before.js b/examples/update-on-async-rendering/updating-external-data-when-props-change-before.js
new file mode 100644
index 00000000000..0b0af809182
--- /dev/null
+++ b/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});
+ }
+ );
+ }
+}
diff --git a/examples/update-on-async-rendering/updating-state-from-props-after.js b/examples/update-on-async-rendering/updating-state-from-props-after.js
new file mode 100644
index 00000000000..19b920a9969
--- /dev/null
+++ b/examples/update-on-async-rendering/updating-state-from-props-after.js
@@ -0,0 +1,24 @@
+// After
+class ExampleComponent extends React.Component {
+ // Initialize state in constructor,
+ // Or with a property initializer.
+ // highlight-range{1-4}
+ state = {
+ isScrollingDown: false,
+ lastRow: null,
+ };
+
+ // highlight-range{1-8}
+ static getDerivedStateFromProps(nextProps, prevState) {
+ if (nextProps.currentRow !== prevState.lastRow) {
+ return {
+ isScrollingDown:
+ nextProps.currentRow > prevState.lastRow,
+ lastRow: nextProps.currentRow,
+ };
+ }
+
+ // Return null to indicate no change to state.
+ return null;
+ }
+}
diff --git a/examples/update-on-async-rendering/updating-state-from-props-before.js b/examples/update-on-async-rendering/updating-state-from-props-before.js
new file mode 100644
index 00000000000..03dc23cf908
--- /dev/null
+++ b/examples/update-on-async-rendering/updating-state-from-props-before.js
@@ -0,0 +1,16 @@
+// Before
+class ExampleComponent extends React.Component {
+ state = {
+ isScrollingDown: false,
+ };
+
+ // highlight-range{1-8}
+ componentWillReceiveProps(nextProps) {
+ if (this.props.currentRow !== nextProps.currentRow) {
+ this.setState({
+ isScrollingDown:
+ nextProps.currentRow > this.props.currentRow,
+ });
+ }
+ }
+}
diff --git a/examples/update-on-async-rendering/using-react-lifecycles-compat.js b/examples/update-on-async-rendering/using-react-lifecycles-compat.js
new file mode 100644
index 00000000000..925f64acb18
--- /dev/null
+++ b/examples/update-on-async-rendering/using-react-lifecycles-compat.js
@@ -0,0 +1,16 @@
+import React from 'react';
+// highlight-next-line
+import polyfill from 'react-lifecycles-compat';
+
+class ExampleComponent extends React.Component {
+ // highlight-next-line
+ static getDerivedStateFromProps(nextProps, prevState) {
+ // Your state update logic here ...
+ }
+}
+
+// Polyfill your component to work with older versions of React:
+// highlight-next-line
+polyfill(ExampleComponent);
+
+export default ExampleComponent;
diff --git a/src/theme.js b/src/theme.js
index 9da7e3e47b3..5fba925d7ef 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -336,7 +336,7 @@ const sharedStyles = {
},
'& li': {
- marginTop: 20,
+ marginTop: 10,
},
'& li.button-newapp': {
@@ -345,6 +345,7 @@ const sharedStyles = {
'& ol, & ul': {
marginLeft: 20,
+ marginTop: 10,
},
},