HTTP
The ability to make HTTP calls is not built-in to React.
React applications use one of the following to make HTTP
calls:
Axios
libraryfetch
API (built-in to modern browsers)
Setup Backend
Before making HTTP calls we need to setup a backend following these directions.
Axios
Install the axios library.
npm install axiosAxios is a
Promise
based HTTP client for thebrowser
and node.js.Add a script tag to the library in
index.html
.<!DOCTYPE html><html lang="en"><head>...</head><body><div id="root"></div>...<script src="/node_modules/@babel/standalone/babel.min.js"></script>+ <script src="/node_modules/axios/dist/axios.js"></script><script type="text/babel" src="/main.js"></script></body></html>Try the following code in
main.js
:const okUrl = "http://localhost:3000/photos?_page=1&_limit=100";const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";const serverErrorUrl = "https://httpstat.us/500";// const urls hereaxios.get(okUrl).then((response) => response.data).then((data) => console.log(data));Open the Chrome DevTools console and you should see the data being logged.
Update the url to an endpoint that throws a server error and update the code to catch the error.
// const urls hereaxios+ .get(serverErrorUrl).then(response => response.data).then(data => console.log(data))+ .catch(error => console.log(error));You should see the following logged in the console.
VM599:1 GET https://httpstat.us/500 500 (Internal Server Error)Error: Request failed with status code 500Try these other urls that also return errors and verify they are logged.
const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";
Fetch
The fetch specification differs from jQuery.ajax()
and axios
in three main ways:
The entire response is returned instead of the JSON data being already parsed (deserialized) into a JavaScript object.
The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.
- By default, fetch won't send or receive any cookies from the server, resulting in unauthenticated requests if the site relies on maintaining a user session (to send cookies, the credentials init option must be set).
The Fetch API
is now standard in modern browsers and does not require an additional install.
Note that you will need to use a polyfill if you need to support IE browsers. See the can I use feature page for fetch for more information. The most commonly used polyfill is isomorphic-fetch.
Try the following code in
main.js
:// const urls herefetch(okUrl).then((response) => console.log(response));Open the Chrome DevTools console and you should see the response object being logged. Notice that the body property is a readable stream object but you can't yet see the data.
Update the code to read the body stream and parse the JSON in the body of the request into a JavaScript object.
// const urls herefetch(okUrl).then((response) => {console.log(response);return response;}).then((response) => response.json()).then((data) => console.log(data));In the console you will see the response as well as the data (parsed body) begin logged.
Update the url to an endpoint that throws a server error and update the code to catch the error.
// const urls here+ fetch(serverErrorUrl).then(response => {console.log(response);return response;}).then(response => response.json()).then(data => console.log(data));+ .catch(error => console.log(error));You should see the following logged in the console.
GET https://httpstat.us/500 500 (Internal Server Error)Response {type: "cors", url: "https://httpstat.us/500", redirected: false, status: 500, ok: false, …}SyntaxError: Unexpected token I in JSON at position 4The catch caught an error thrown on the line shown below. The error occurred when parsing the json into a JavaScript object.
.then(response => response.json());Remember,
fetch()
won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false)To see this happening change .json() to .text()
- .then(response => response.json());+ then(response => response.text());You should now see the error:
500 Internal Server Error
being logged to the console.The fetch API doesn't consider server errors to be an error, the request was made and a response was returned. The response just happened to include an error message in the body and that body is not of
content-type: application/json
hence the error when attempt to parse it asjson
.Change .text() back to .json()
+ .then(response => response.json());- then(response => response.text());Add the following code to treat a response with a status set to false as an error.
// const urls herefetch(serverErrorUrl).then(response => {console.log(response);return response;})+ .then(response => {+ if (!response.ok) throw new Error(response.statusText);+ return response;+ }).then(response => response.json()).then(data => console.log(data)).catch(error => console.log(error));You should again see the error:
Internal Server Error
being logged to the console.Now that we have things working we can remove the logging of the full response.
// const urls herefetch(serverErrorUrl)- .then(response => {- console.log(response);- return response;- }).then(response => {if (!response.ok) throw new Error(response.statusText);return response;}).then(response => response.json()).then(data => console.log(data)).catch(error => console.log(error));We can also pull the logic to parse the JSON body and handle the error into reusable functions.
Try these other urls that also return errors and verify they are logged properly.
const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";
In React
Now that we understand the fundamental underlying concepts lets render this data in React.
When to Load Data?
- In a function component, you should make your AJAX calls in a
useEffect
hook. When the data or the error returns you can use your set state updater function returned fromuseState
to update the state. - In a class component, you should make your AJAX calls in the
componentDidMount
lifecycle method. This is so you can usesetState
to update your component when the data is retrieved.
Loading
Since AJAX calls don't always return immediately (they are asynchronous) it is common practice to show a loading indicator when the HTTP request is in flight.
Error Handling
If an error occurs while making the request or when it returns we need to either display that error or translate it to a more user friendly message and then display the error.
Initially, we'll just display the error from the server and then later we will see how to translate that error to something more user friendly.
Lists
If the data is returned successfully, we can use what we learned in the list section to display the data.
! Remember we need to set a key on the list items.
Function Component Example
Try the following code in
main.js
const okUrl = "http://localhost:3000/photos?_page=1&_limit=100";const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";const serverErrorUrl = "https://httpstat.us/500";function PhotoList() {const [loading, setLoading] = React.useState(false);const [photos, setPhotos] = React.useState([]);const [error, setError] = React.useState(null);function toUserError(error) {console.log("Call API to log the raw error. ", error);return "There was an error loading the photos.";}React.useEffect(() => {setLoading(true);fetch(okUrl).then((response) => {if (!response.ok) throw new Error(response.statusText);return response;}).then((response) => response.json()).then((data) => {setError(null);setPhotos(data);setLoading(false);}).catch((error) => {const userError = toUserError(error);setError(userError);setLoading(false);});}, []);if (error) {return <div>{error}</div>;} else if (loading) {return <div>Loading...</div>;} else {return (<ul>{photos.map((photo) => {return (<li key={photo.id}><img src={photo.thumbnailUrl} alt={photo.title} /><h3>{photo.title}</h3></li>);})}</ul>);}}ReactDOM.createRoot(document.getElementById("root")).render(<PhotoList />);Try these other urls that return errors and verify they are logged properly.
const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";const serverErrorUrl = "https://httpstat.us/500";
Class Component Example
Try the following code in
main.js
const okUrl = "http://localhost:3000/photos?_page=1&_limit=100";const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";const serverErrorUrl = "https://httpstat.us/500";class PhotoList extends React.Component {state = {loading: false,photos: [],error: undefined,};componentDidMount() {this.setState({ loading: true });fetch(okUrl).then((response) => {if (!response.ok) throw new Error(response.statusText);return response;}).then((response) => response.json()).then((data) => {this.setState({ photos: data, loading: false });}).catch((error) => {const userError = this.toUserError(error);this.setState({ error: userError, loading: false });});}toUserError(error) {console.log("Call API to log the raw error. ", error);return "There was an error loading the photos.";}render() {const { loading, photos, error } = this.state;if (error) {return <div>{error}</div>;} else if (loading) {return <div>Loading...</div>;} else {return (<ul>{photos.map((photo) => {return (<li key={photo.id}><img src={photo.thumbnailUrl} alt={photo.title} /><p>{photo.title}</p></li>);})}</ul>);}}}ReactDOM.createRoot(document.getElementById("root")).render(<PhotoList />);Try these other urls that return errors and verify they are logged properly.
const notFoundErrorUrl = "https://httpstat.us/404";const forbiddenErrorUrl = "https://httpstat.us/403";const serverErrorUrl = "https://httpstat.us/500";
Reuse via API object
After you get comfortable using Axios
and/or the fetch API
and rendering the result in a React component, consider pulling the data access code into a reusable object. The benefit to doing this is that multiple components can make the same API call and convert to more user friendly error messages without repeating the code involved.
React is not very prescriptive about file names but their documentation does show these files being named with an API suffix (for example ProfileAPI.js).
Review the examples below (using the fetch API). If time permits get the example running in main.js