From b06e0d543c3441920a0b1a6dcb817090be2fbe6c Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Tue, 25 Oct 2016 23:36:18 +0000 Subject: [PATCH 01/15] Add a guide on loading data asynchronously --- docs/_data/nav_docs.yml | 3 +- docs/docs/loading-data-asynchronously.md | 114 +++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 docs/docs/loading-data-asynchronously.md diff --git a/docs/_data/nav_docs.yml b/docs/_data/nav_docs.yml index 1be1ce3c07db..c329090899a5 100644 --- a/docs/_data/nav_docs.yml +++ b/docs/_data/nav_docs.yml @@ -32,6 +32,8 @@ title: JSX In Depth - id: typechecking-with-proptypes title: Typechecking With PropTypes + - id: loading-data-asynchronously + title: Loading Data Asynchronously - id: refs-and-the-dom title: Refs and the DOM - id: optimizing-performance @@ -80,4 +82,3 @@ title: Shallow Compare - id: two-way-binding-helpers title: Two-way Binding Helpers - diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md new file mode 100644 index 000000000000..6a085bda343b --- /dev/null +++ b/docs/docs/loading-data-asynchronously.md @@ -0,0 +1,114 @@ +--- +id: loading-data-asynchronously +title: Loading Data Asynchronously +permalink: docs/loading-data-asynchronously.html +prev: typechecking-with-proptypes.html +--- + + +Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](https://facebook.github.io/react/docs/react-component.html#componentdidmount). + +In the following example we use the `fetch` API to retrieve information about Facebook's Gists on GitHub and store them in the state. + +```javascript{10-14} +class Gists extends React.Component { + constructor(props) { + super(props); + + this.state = { + gists: [], + }; + } + + componentDidMount() { + fetch('https://api.github.com/users/facebook/gists') + .then(res => res.json()) + .then(gists => this.setState({ gists })); + } + + render() { + const { gists } = this.state; + + return ( +
+

Gists by facebook

+ {gists.map(gist =>

{gist.id}

)} +
+ ); + } +} +``` + +Note that, the component will perform an initial render without any of the network data. When the fetch promise resolves, it calls `setState` and the component is rerendered. + +## Updates + +If the props change, we might need to fetch new data for the updated props. The `componentDidUpdate` [lifecycle hook](https://facebook.github.io/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. + +Building on the previous, we will pass the username as a prop instead and fetch new gists when it changes: + +```javascript{10-20} +class Gists extends React.Component { + componentDidMount() { + const { username } = this.props; + + fetch(`https://api.github.com/users/${username}/gists`) + .then(res => res.json()) + .then(gists => this.setState({ gists })); + } + + componentDidUpdate(prevProps) { + const { username } = this.props; + + // Make sure that the `username` prop did change before + // we initiate a network request. + if (username !== prevProps.username) { + fetch(`https://api.github.com/users/${username}/gists`) + .then(res => res.json()) + .then(gists => this.setState({ gists })); + } + } + + render() { + const { username } = this.props; + const { gists } = this.state; + + return ( +
+

Gists by {username}.

+ {gists.map(gist =>

{gist.id}

)} +
+ ); + } + + /* ... */ +} +``` + +We can extract the common code in `componentDidMount` and `componentDidUpdate` into a new method, `fetchGists`, and call that in both lifecycle hooks. + +```javascript +class Gists extends React.Component { + componentDidMount() { + this.fetchGists(); + } + + componentDidUpdate(prevProps) { + if (this.props.username !== prevProps.username) { + this.fetchGists(); + } + } + + fetchGists() { + const { username } = this.props; + + fetch(`https://api.github.com/users/${username}/gists`) + .then(res => res.json()) + .then(gists => this.setState({ gists })); + } + + /* ... */ +} +``` + +[Try it out on CodePen.](http://codepen.io/rthor/pen/kkqrQx?editors=0010) From 382fee457d311e3deb0bd3d75f8195e1b65c0f6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ragnar=20=C3=9E=C3=B3r=20Valgeirsson?= Date: Wed, 26 Oct 2016 20:46:31 +0000 Subject: [PATCH 02/15] Add missing word --- docs/docs/loading-data-asynchronously.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index 6a085bda343b..e841c9b8b6fe 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -45,7 +45,7 @@ Note that, the component will perform an initial render without any of the netwo If the props change, we might need to fetch new data for the updated props. The `componentDidUpdate` [lifecycle hook](https://facebook.github.io/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. -Building on the previous, we will pass the username as a prop instead and fetch new gists when it changes: +Building on the previous example, we will pass the username as a prop instead and fetch new gists when it changes: ```javascript{10-20} class Gists extends React.Component { From b5453008f7649f81c5e3816d82d05cf8e79677fb Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Thu, 27 Oct 2016 17:55:58 +0000 Subject: [PATCH 03/15] Relative links --- docs/docs/loading-data-asynchronously.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index e841c9b8b6fe..efe865851efa 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -6,7 +6,7 @@ prev: typechecking-with-proptypes.html --- -Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](https://facebook.github.io/react/docs/react-component.html#componentdidmount). +Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). In the following example we use the `fetch` API to retrieve information about Facebook's Gists on GitHub and store them in the state. @@ -43,7 +43,7 @@ Note that, the component will perform an initial render without any of the netwo ## Updates -If the props change, we might need to fetch new data for the updated props. The `componentDidUpdate` [lifecycle hook](https://facebook.github.io/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. +If the props change, we might need to fetch new data for the updated props. The `componentDidUpdate` [lifecycle hook](/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. Building on the previous example, we will pass the username as a prop instead and fetch new gists when it changes: From 34aedac917e01e91df8ae77bcfdcd910a8fb36e0 Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Thu, 27 Oct 2016 22:01:01 +0000 Subject: [PATCH 04/15] More concise code snippets --- docs/docs/loading-data-asynchronously.md | 46 +++++++++++++++--------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index efe865851efa..2ab8bfcb9a67 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -5,19 +5,15 @@ permalink: docs/loading-data-asynchronously.html prev: typechecking-with-proptypes.html --- - Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). In the following example we use the `fetch` API to retrieve information about Facebook's Gists on GitHub and store them in the state. -```javascript{10-14} +```javascript{7-11} class Gists extends React.Component { constructor(props) { super(props); - - this.state = { - gists: [], - }; + this.state = {gists: []}; } componentDidMount() { @@ -28,7 +24,6 @@ class Gists extends React.Component { render() { const { gists } = this.state; - return (

Gists by facebook

@@ -47,11 +42,15 @@ If the props change, we might need to fetch new data for the updated props. The Building on the previous example, we will pass the username as a prop instead and fetch new gists when it changes: -```javascript{10-20} +```javascript{7-12,14-23} class Gists extends React.Component { + constructor(props) { + super(props); + this.state = {gists: []}; + } + componentDidMount() { const { username } = this.props; - fetch(`https://api.github.com/users/${username}/gists`) .then(res => res.json()) .then(gists => this.setState({ gists })); @@ -59,7 +58,6 @@ class Gists extends React.Component { componentDidUpdate(prevProps) { const { username } = this.props; - // Make sure that the `username` prop did change before // we initiate a network request. if (username !== prevProps.username) { @@ -72,23 +70,27 @@ class Gists extends React.Component { render() { const { username } = this.props; const { gists } = this.state; - return (

Gists by {username}.

- {gists.map(gist =>

{gist.id}

)} + {gists.map(gist => +

{gist.id}

+ )}
); } - - /* ... */ } ``` We can extract the common code in `componentDidMount` and `componentDidUpdate` into a new method, `fetchGists`, and call that in both lifecycle hooks. -```javascript +```javascript{8,13,17-22} class Gists extends React.Component { + constructor(props) { + super(props); + this.state = {gists: []}; + } + componentDidMount() { this.fetchGists(); } @@ -101,13 +103,23 @@ class Gists extends React.Component { fetchGists() { const { username } = this.props; - fetch(`https://api.github.com/users/${username}/gists`) .then(res => res.json()) .then(gists => this.setState({ gists })); } - /* ... */ + render() { + const { username } = this.props; + const { gists } = this.state; + return ( +
+

Gists by {username}.

+ {gists.map(gist => +

{gist.id}

+ )} +
+ ); + } } ``` From 4c92f0eaa0297952bf78437bb078405751d81401 Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Thu, 27 Oct 2016 22:06:51 +0000 Subject: [PATCH 05/15] Pitfalls section that explains cancellation issues --- docs/docs/loading-data-asynchronously.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index 2ab8bfcb9a67..e202de01b93f 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -124,3 +124,13 @@ class Gists extends React.Component { ``` [Try it out on CodePen.](http://codepen.io/rthor/pen/kkqrQx?editors=0010) + +## Pitfalls + +An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the results of the new one. If a promise is pending when a component is updated, the pending promise should be cancelled before a new one is created. + +Additionally, a component can unmount while a promise is pending. To avoid unexpected behavior and memory leaks when this happens, be sure to also cancel all pending promises in the `componentWillUnmount` [lifecycle hook](/react/docs/react-component.html#componentwillunmount). + +> **Caveat:** +> +> A standard for cancelling promises is still being worked on. Therefore, some workarounds, or 3rd party libraries, may be needed at this point. From 55ead663a23ca79fd56921b37d78d203fb46d904 Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Thu, 27 Oct 2016 22:44:28 +0000 Subject: [PATCH 06/15] Add information on the fetch API --- docs/docs/loading-data-asynchronously.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index e202de01b93f..2e42675e4e0e 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -7,7 +7,7 @@ prev: typechecking-with-proptypes.html Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). -In the following example we use the `fetch` API to retrieve information about Facebook's Gists on GitHub and store them in the state. +In the following example we use the `fetch` [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to retrieve information about Facebook's Gists on GitHub and store them in the state. ```javascript{7-11} class Gists extends React.Component { @@ -34,7 +34,11 @@ class Gists extends React.Component { } ``` -Note that, the component will perform an initial render without any of the network data. When the fetch promise resolves, it calls `setState` and the component is rerendered. +The component will perform an initial render without any of the network data. When the fetch promise resolves, it calls `setState` and the component is rerendered. + +> **Note:** +> +> The API specification for `fetch` has not been stabilized and browser support is not quite there yet. To use `fetch` today, a [polyfill](https://github.com/github/fetch) is available for non-supporting browsers. If you're using Create React App, a polyfill is available by default. ## Updates From 4b1fa64cd462df8ee9e7be1f59efdd10c52ec727 Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Thu, 27 Oct 2016 22:56:28 +0000 Subject: [PATCH 07/15] Other advanced doc files don't have previous links --- docs/docs/loading-data-asynchronously.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index 2e42675e4e0e..da6642ab82e9 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -2,7 +2,6 @@ id: loading-data-asynchronously title: Loading Data Asynchronously permalink: docs/loading-data-asynchronously.html -prev: typechecking-with-proptypes.html --- Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). From 461082cc02946b950908e0259b5f0352750588ac Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Sun, 30 Oct 2016 21:42:37 +0000 Subject: [PATCH 08/15] Example that illustrates how to ignore a response --- docs/docs/loading-data-asynchronously.md | 59 ++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index da6642ab82e9..a6f7ec502595 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -130,10 +130,59 @@ class Gists extends React.Component { ## Pitfalls -An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the results of the new one. If a promise is pending when a component is updated, the pending promise should be cancelled before a new one is created. +An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the result of the new one. If a promise is pending when a component is updated, the result of the first promise should be ignored before a new one is created. -Additionally, a component can unmount while a promise is pending. To avoid unexpected behavior and memory leaks when this happens, be sure to also cancel all pending promises in the `componentWillUnmount` [lifecycle hook](/react/docs/react-component.html#componentwillunmount). +Additionally, a component can unmount while a promise is pending. React warns you if you call `setState()` on unmounted components to prevent memory leaks. Some data fetching APIs allow you to cancel requests, and this is preferable when a component unmounts. For APIs such as `fetch()` that don't offer a cancellation mechanism, you need to keep track of whether the component is mounted to avoid seeing warnings. Here is how we could implement this: -> **Caveat:** -> -> A standard for cancelling promises is still being worked on. Therefore, some workarounds, or 3rd party libraries, may be needed at this point. +```javascript{8,15,19-21,23,26,29-34} +class Gists extends React.Component { + constructor(props) { + super(props); + this.state = { gists: [] }; + } + + componentDidMount() { + this.fetchGists(this.props.username); + } + + componentDidUpdate(prevProps) { + // Make sure that the `username` prop did change before + // we initiate a network request. + if (this.props.username !== prevProps.username) { + this.fetchGists(this.props.username); + } + } + + componentWillUnmount() { + this.isUnmounted = true; + } + + fetchGists(username) { + fetch(`https://api.github.com/users/${username}/gists`) + .then(data => data.json()) + .then(gists => this.handleFetchSuccess(username, gists)); + } + + handleFetchSuccess(username, gists) { + if (this.isUnmounted || username !== this.props.username) { + return; + } + this.setState({ gists }); + } + + render() { + const { username } = this.props; + const { gists } = this.state; + return ( +
+

Gists by {username}.

+ {gists.map(gist => +

{gist.id}

+ )} +
+ ); + } +} +``` + +[Try it out on CodePen.](http://codepen.io/rthor/pen/edweqz?editors=0010) From 763c79083d3629f5697cc1c9c49042b1f9b8857e Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Sun, 30 Oct 2016 22:22:54 +0000 Subject: [PATCH 09/15] Add an async / await example --- docs/docs/loading-data-asynchronously.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index a6f7ec502595..7470b8e03ace 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -128,6 +128,22 @@ class Gists extends React.Component { [Try it out on CodePen.](http://codepen.io/rthor/pen/kkqrQx?editors=0010) +We can simplify the `fetchGists` method by using the [`async / await`](https://tc39.github.io/ecmascript-asyncawait/) feature: + +```javascript{1,3-4} +async fetchGists() { + const { username } = this.props; + const data = await fetch(`https://api.github.com/users/${username}/gists`); + this.setState({gists: await data.json()}); +} +``` + +[Try it out on CodePen.](https://codepen.io/rthor/pen/xEoWod?editors=0010) + +> **Note:** +> +> `async / await` is still a proposal for the ECMAScript spec and therefore hasn't been implemented in most browsers. To use it today, a [Babel](http://babeljs.io/docs/plugins/transform-async-to-generator/) (or similar) transform is needed. If you're using Create React App, it works by default. + ## Pitfalls An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the result of the new one. If a promise is pending when a component is updated, the result of the first promise should be ignored before a new one is created. From c1a36a1ebb121549bd06e3944735a2e37f0dff38 Mon Sep 17 00:00:00 2001 From: Ragnar Valgeirsson Date: Sun, 30 Oct 2016 22:49:30 +0000 Subject: [PATCH 10/15] Add an intro --- docs/docs/loading-data-asynchronously.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index 7470b8e03ace..b11406543729 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -4,6 +4,10 @@ title: Loading Data Asynchronously permalink: docs/loading-data-asynchronously.html --- +React has no special capabilities for dealing with asynchronous network requests and a 3rd-party library or browser API is needed to perform them. If a component needs to have its UI respond to new data arriving, it has to call `setState` to rerender itself. + +## Initial Render + Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). In the following example we use the `fetch` [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to retrieve information about Facebook's Gists on GitHub and store them in the state. @@ -148,7 +152,7 @@ async fetchGists() { An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the result of the new one. If a promise is pending when a component is updated, the result of the first promise should be ignored before a new one is created. -Additionally, a component can unmount while a promise is pending. React warns you if you call `setState()` on unmounted components to prevent memory leaks. Some data fetching APIs allow you to cancel requests, and this is preferable when a component unmounts. For APIs such as `fetch()` that don't offer a cancellation mechanism, you need to keep track of whether the component is mounted to avoid seeing warnings. Here is how we could implement this: +Additionally, a component can unmount while a promise is pending. React warns you if you call `setState` on unmounted components to prevent memory leaks. Some data fetching APIs allow you to cancel requests, and this is preferable when a component unmounts. For APIs such as `fetch` that don't offer a cancellation mechanism, you need to keep track of whether the component is mounted to avoid seeing warnings. Here is how we could implement this: ```javascript{8,15,19-21,23,26,29-34} class Gists extends React.Component { From 72632e9c706c34851c592f06608402115bda2d6a Mon Sep 17 00:00:00 2001 From: Kevin Lacker Date: Wed, 9 Nov 2016 15:21:53 -0800 Subject: [PATCH 11/15] little style tweak 3rd-party => third party --- docs/docs/loading-data-asynchronously.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index b11406543729..15a3142dd63b 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -4,7 +4,7 @@ title: Loading Data Asynchronously permalink: docs/loading-data-asynchronously.html --- -React has no special capabilities for dealing with asynchronous network requests and a 3rd-party library or browser API is needed to perform them. If a component needs to have its UI respond to new data arriving, it has to call `setState` to rerender itself. +React has no special capabilities for dealing with asynchronous network requests and a third party library or browser API is needed to perform them. If a component needs to have its UI respond to new data arriving, it has to call `setState` to rerender itself. ## Initial Render From a9a60079f41ab443e3bbebfca20f1f3dea8b1651 Mon Sep 17 00:00:00 2001 From: Kevin Lacker Date: Wed, 9 Nov 2016 15:22:27 -0800 Subject: [PATCH 12/15] tweak put name in link --- docs/docs/loading-data-asynchronously.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index 15a3142dd63b..b270d653429e 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -8,7 +8,7 @@ React has no special capabilities for dealing with asynchronous network requests ## Initial Render -Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). +Often, the data that a component needs is not available at initial render. We can load data asynchronously in the [`componentDidMount` lifecycle hook](/react/docs/react-component.html#componentdidmount). In the following example we use the `fetch` [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to retrieve information about Facebook's Gists on GitHub and store them in the state. From 74cc9784197673ec22496578f46d5bea0a133a4c Mon Sep 17 00:00:00 2001 From: Kevin Lacker Date: Wed, 9 Nov 2016 15:26:43 -0800 Subject: [PATCH 13/15] Update loading-data-asynchronously.md --- docs/docs/loading-data-asynchronously.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index b270d653429e..ecb161667302 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -45,7 +45,7 @@ The component will perform an initial render without any of the network data. Wh ## Updates -If the props change, we might need to fetch new data for the updated props. The `componentDidUpdate` [lifecycle hook](/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. +If the props change, we might need to fetch new data for the updated props. The [`componentDidUpdate` lifecycle hook](/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. Building on the previous example, we will pass the username as a prop instead and fetch new gists when it changes: From 1c4c1c9062daf72a93f6cfe10e82d4069da32e6e Mon Sep 17 00:00:00 2001 From: Mojtaba Dashtinejad Date: Mon, 16 Jan 2017 16:41:26 +0330 Subject: [PATCH 14/15] change first example with axios + codepen link --- docs/docs/loading-data-asynchronously.md | 28 +++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index ecb161667302..bc9b58ed9d37 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -10,9 +10,17 @@ React has no special capabilities for dealing with asynchronous network requests Often, the data that a component needs is not available at initial render. We can load data asynchronously in the [`componentDidMount` lifecycle hook](/react/docs/react-component.html#componentdidmount). -In the following example we use the `fetch` [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to retrieve information about Facebook's Gists on GitHub and store them in the state. +In the following example we use the [axios](https://github.com/mzabriskie/axios) to retrieve information about Facebook's Gists on GitHub and store them in the state. So first install it: + +``` +$ npm install axios --save +``` + +And you can use it in your project: + +```javascript{1,9-13} +import axios from 'axios'; -```javascript{7-11} class Gists extends React.Component { constructor(props) { super(props); @@ -20,9 +28,9 @@ class Gists extends React.Component { } componentDidMount() { - fetch('https://api.github.com/users/facebook/gists') - .then(res => res.json()) - .then(gists => this.setState({ gists })); + axios.get('https://api.github.com/users/facebook/gists') + .then(response => this.setState({ gists: response.data })) + .catch(error => console.log(error)); } render() { @@ -30,18 +38,18 @@ class Gists extends React.Component { return (

Gists by facebook

- {gists.map(gist =>

{gist.id}

)} + {gists.map(gist => +

{gist.id}

+ )}
); } } ``` -The component will perform an initial render without any of the network data. When the fetch promise resolves, it calls `setState` and the component is rerendered. +The component will perform an initial render without any of the network data. When the axios promise resolves, it calls `setState` and the component is rerendered. -> **Note:** -> -> The API specification for `fetch` has not been stabilized and browser support is not quite there yet. To use `fetch` today, a [polyfill](https://github.com/github/fetch) is available for non-supporting browsers. If you're using Create React App, a polyfill is available by default. +[Try it on CodePen.](http://codepen.io/dashtinejad/pen/wgzEXJ?editors=0011) ## Updates From 4e0f3ce543551a561a73c0bbc6414a22f4060a19 Mon Sep 17 00:00:00 2001 From: Mojtaba Dashtinejad Date: Mon, 30 Jan 2017 16:21:39 +0330 Subject: [PATCH 15/15] replace fetch with axios --- docs/docs/loading-data-asynchronously.md | 185 ++++++++--------------- 1 file changed, 62 insertions(+), 123 deletions(-) diff --git a/docs/docs/loading-data-asynchronously.md b/docs/docs/loading-data-asynchronously.md index bc9b58ed9d37..654dae32d273 100644 --- a/docs/docs/loading-data-asynchronously.md +++ b/docs/docs/loading-data-asynchronously.md @@ -10,7 +10,7 @@ React has no special capabilities for dealing with asynchronous network requests Often, the data that a component needs is not available at initial render. We can load data asynchronously in the [`componentDidMount` lifecycle hook](/react/docs/react-component.html#componentdidmount). -In the following example we use the [axios](https://github.com/mzabriskie/axios) to retrieve information about Facebook's Gists on GitHub and store them in the state. So first install it: +In the following example we use the [axios](https://github.com/mzabriskie/axios) to retrieve information about Facebook's Repos on GitHub and store them in the state. So first install it: ``` $ npm install axios --save @@ -21,25 +21,24 @@ And you can use it in your project: ```javascript{1,9-13} import axios from 'axios'; -class Gists extends React.Component { +class Repos extends React.Component { constructor(props) { super(props); - this.state = {gists: []}; + this.state = {repos: []}; } componentDidMount() { - axios.get('https://api.github.com/users/facebook/gists') - .then(response => this.setState({ gists: response.data })) + axios.get('https://api.github.com/users/facebook/repos') + .then(response => this.setState({ repos: response.data })) .catch(error => console.log(error)); } render() { - const { gists } = this.state; return (
-

Gists by facebook

- {gists.map(gist => -

{gist.id}

+

Repos by facebook

+ {this.state.repos.map(repo => +
{repo.name}
)}
); @@ -55,162 +54,102 @@ The component will perform an initial render without any of the network data. Wh If the props change, we might need to fetch new data for the updated props. The [`componentDidUpdate` lifecycle hook](/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. -Building on the previous example, we will pass the username as a prop instead and fetch new gists when it changes: +Building on the previous example, we will pass the username as a prop instead and fetch new repos when it changes: -```javascript{7-12,14-23} -class Gists extends React.Component { +```javascript{7-11,,13-15,17-21,35,38,44-47,53} +class Repos extends React.Component { constructor(props) { super(props); - this.state = {gists: []}; + this.state = {repos: []}; + } + + fetchRepos() { + axios.get(`https://api.github.com/users/${this.props.username}/repos`) + .then(response => this.setState({ repos: response.data })) + .catch(error => console.log(error)); } componentDidMount() { - const { username } = this.props; - fetch(`https://api.github.com/users/${username}/gists`) - .then(res => res.json()) - .then(gists => this.setState({ gists })); + this.fetchRepos(); } componentDidUpdate(prevProps) { - const { username } = this.props; - // Make sure that the `username` prop did change before - // we initiate a network request. - if (username !== prevProps.username) { - fetch(`https://api.github.com/users/${username}/gists`) - .then(res => res.json()) - .then(gists => this.setState({ gists })); + if (this.props.username != prevProps.username) { + this.fetchRepos(); } } render() { - const { username } = this.props; - const { gists } = this.state; return (
-

Gists by {username}.

- {gists.map(gist => -

{gist.id}

+

Repos by {this.props.username}

+ {this.state.repos.map(repo => +
{repo.name}
)}
); } } -``` - -We can extract the common code in `componentDidMount` and `componentDidUpdate` into a new method, `fetchGists`, and call that in both lifecycle hooks. -```javascript{8,13,17-22} -class Gists extends React.Component { +class App extends React.Component { constructor(props) { super(props); - this.state = {gists: []}; - } - - componentDidMount() { - this.fetchGists(); - } - - componentDidUpdate(prevProps) { - if (this.props.username !== prevProps.username) { - this.fetchGists(); - } - } - - fetchGists() { - const { username } = this.props; - fetch(`https://api.github.com/users/${username}/gists`) - .then(res => res.json()) - .then(gists => this.setState({ gists })); + this.state = {username: 'facebook'}; } render() { - const { username } = this.props; - const { gists } = this.state; return (
-

Gists by {username}.

- {gists.map(gist => -

{gist.id}

- )} + + + +
); } } -``` - -[Try it out on CodePen.](http://codepen.io/rthor/pen/kkqrQx?editors=0010) -We can simplify the `fetchGists` method by using the [`async / await`](https://tc39.github.io/ecmascript-asyncawait/) feature: - -```javascript{1,3-4} -async fetchGists() { - const { username } = this.props; - const data = await fetch(`https://api.github.com/users/${username}/gists`); - this.setState({gists: await data.json()}); -} +ReactDOM.render(, document.getElementById('app')) ``` -[Try it out on CodePen.](https://codepen.io/rthor/pen/xEoWod?editors=0010) - -> **Note:** -> -> `async / await` is still a proposal for the ECMAScript spec and therefore hasn't been implemented in most browsers. To use it today, a [Babel](http://babeljs.io/docs/plugins/transform-async-to-generator/) (or similar) transform is needed. If you're using Create React App, it works by default. +[Try it on CodePen.](http://codepen.io/dashtinejad/pen/zNpzVW?editors=0011) -## Pitfalls +## Cancellation An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the result of the new one. If a promise is pending when a component is updated, the result of the first promise should be ignored before a new one is created. +Some data fetching APIs allow you to cancel requests, and for axios, we use [Cancellation](https://github.com/mzabriskie/axios#cancellation) by token: -Additionally, a component can unmount while a promise is pending. React warns you if you call `setState` on unmounted components to prevent memory leaks. Some data fetching APIs allow you to cancel requests, and this is preferable when a component unmounts. For APIs such as `fetch` that don't offer a cancellation mechanism, you need to keep track of whether the component is mounted to avoid seeing warnings. Here is how we could implement this: - -```javascript{8,15,19-21,23,26,29-34} -class Gists extends React.Component { - constructor(props) { - super(props); - this.state = { gists: [] }; - } - - componentDidMount() { - this.fetchGists(this.props.username); - } - - componentDidUpdate(prevProps) { - // Make sure that the `username` prop did change before - // we initiate a network request. - if (this.props.username !== prevProps.username) { - this.fetchGists(this.props.username); - } - } - - componentWillUnmount() { - this.isUnmounted = true; - } - - fetchGists(username) { - fetch(`https://api.github.com/users/${username}/gists`) - .then(data => data.json()) - .then(gists => this.handleFetchSuccess(username, gists)); - } +```javascript{5-8,10-11,14-15,19-23} +class Repos extends React.Component { + // removed for brevity - handleFetchSuccess(username, gists) { - if (this.isUnmounted || username !== this.props.username) { - return; + fetchRepos() { + // cancel the previous request + if (typeof this._source != typeof undefined) { + this._source.cancel('Operation canceled due to new request.') } - this.setState({ gists }); - } - render() { - const { username } = this.props; - const { gists } = this.state; - return ( -
-

Gists by {username}.

- {gists.map(gist => -

{gist.id}

- )} -
- ); - } + // save the new request for cancellation + this._source = axios.CancelToken.source(); + + axios.get(`https://api.github.com/users/${this.props.username}/repos`, + // cancel token used by axios + { cancelToken: this._source.token } + ) + .then(response => this.setState({ repos: response.data })) + .catch(error => { + if (axios.isCancel(error)) { + console.log('Request canceled', error); + } else { + console.log(error); + } + }); + } + + // removed for brevity } ``` -[Try it out on CodePen.](http://codepen.io/rthor/pen/edweqz?editors=0010) +[Try it on CodePen.](http://codepen.io/dashtinejad/pen/Lxejpq?editors=0011) + +[You can clone the whole sourcecode from GitHub](https://github.com/dashtinejad/react-ajax-axios). \ No newline at end of file