Table of Contents
When you develop an application, you will often need to fetch data from a backend or a third-party API. In this article, we will learn different ways to fetch and display data from API in React.
fetch
API.
Fetching data using inbuilt All modern browsers come with an inbuilt fetch Web API, which can be used to fetch data from APIs. In this tutorial, we will be fetching data from the JSON Server APIs.
1import React, { useEffect, useState } from "react"23const UsingFetch = () => {4 const [users, setUsers] = useState([])56 const fetchData = () => {7 fetch("https://jsonplaceholder.typicode.com/users")8 .then(response => {9 return response.json()10 })11 .then(data => {12 setUsers(data)13 })14 }1516 useEffect(() => {17 fetchData()18 }, [])1920 return (21 <div>22 {users.length > 0 && (23 <ul>24 {users.map(user => (25 <li key={user.id}>{user.name}</li>26 ))}27 </ul>28 )}29 </div>30 )31}3233export default UsingFetch
In the above code,
- We have are using a
useEffect
hook, which will be executed once the component is mounted (alternative of componentDidMount in class-based components). Inside theuseEffect
hook, we are callingfetchData
function. - In the
fetchData
function, we are making the API call to fetch users and set the users to a local state. - If users exist, then we are looping through them and displaying their names as a list.
You can learn more about how useEffect works and how to loop through an array in React from my previous articles.
Since the API calls are asynchronous,
fetch
API returns a Promise.
Hence, we chain the then
method with a callback, which will be called when we receive the response from the server/backend.
Since we need the response to be resolved to a JSON, we call .json()
method with the returned response.
Again .json()
return a promise, therefore we need to chain another then
method to resolve the second promise.
Since the then
callbacks have only one line, we can use implicit returns to shorten the fetchData
method as follows:
1const fetchData = () =>2 fetch("https://jsonplaceholder.typicode.com/users")3 .then(response => response.json())4 .then(data => setUsers(data))
Fetching data in React using async-await
In case you like to use async-await syntax instead of then
callbacks, you can write the same example as follows:
1import React, { useEffect, useState } from "react"23const AsyncAwait = () => {4 const [users, setUsers] = useState([])56 const fetchData = async () => {7 const response = await fetch("https://jsonplaceholder.typicode.com/users")8 const data = await response.json()9 setUsers(data)10 }1112 useEffect(() => {13 fetchData()14 }, [])1516 return (17 <div>18 {users.length > 0 && (19 <ul>20 {users.map(user => (21 <li key={user.id}>{user.name}</li>22 ))}23 </ul>24 )}25 </div>26 )27}2829export default AsyncAwait
Make sure that you do not use async-await inside the useEffect hook. If you convert the useEffect hook itself to an async function, then React will show the following warning:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside
Fetching Data in React when a button is clicked
If you want to fetch data conditionally, say when a button is clicked, you can do that as shown below:
1import React, { useState } from "react"23const ButtonClick = () => {4 const [users, setUsers] = useState([])56 const fetchData = () => {7 fetch("https://jsonplaceholder.typicode.com/users")8 .then(response => {9 return response.json()10 })11 .then(data => {12 setUsers(data)13 })14 }1516 return (17 <div>18 <button onClick={fetchData}>Fetch Users</button>19 {users.length > 0 && (20 <ul>21 {users.map(user => (22 <li key={user.id}>{user.name}</li>23 ))}24 </ul>25 )}26 </div>27 )28}2930export default ButtonClick
Here instead of calling fetchData
inside the useEffect hook,
we are passing it to the onClick handler of the button.
Passing a parameter while fetching data
If you want to fetch data based on some parameter, say the id of the user, then you can do by adding it to the URL as shown below. The backtick (``) syntax is known as template literals or string interpolation in JavaScript.
1import React, { useEffect, useState } from "react"23const PassParam = () => {4 const [user, setUser] = useState([])5 const id = 167 const fetchData = () => {8 fetch(`https://jsonplaceholder.typicode.com/users?id=${id}`)9 .then(response => {10 return response.json()11 })12 .then(data => {13 setUser(data[0].name)14 })15 }1617 useEffect(() => {18 fetchData()19 }, [])2021 return <div>Name: {user}</div>22}2324export default PassParam
Fetching data in React based on user input (onChange)
If you want to fetch data based on user input, say user searching for a name, then you achieve it with the following code:
1import React, { useState } from "react"23const SearchUser = () => {4 const [users, setUsers] = useState([])56 const fetchData = e => {7 const query = e.target.value8 fetch(`https://jsonplaceholder.typicode.com/users?q=${query}`)9 .then(response => {10 return response.json()11 })12 .then(data => {13 setUsers(data)14 })15 }1617 return (18 <div>19 <input onChange={fetchData} label="Search User" />20 {users.length > 0 && (21 <ul>22 {users.map(user => (23 <li key={user.id}>{user.name}</li>24 ))}25 </ul>26 )}27 </div>28 )29}3031export default SearchUser
In the above code, we have modified the previous example to take user input by binding an onChange handler.
The above example will search for all the data belonging to the user, not just the name.
Displaying Loading state when fetching data from API in React
It is always a good practice to display an indicator (a loader) to the user while fetching data so that the user wouldn't wonder what is happening after seeing a blank screen while the data is being loaded.
We can display a loading message (or a spinner) by making use of a local state.
1import React, { useEffect, useState } from "react"23const LoadingText = () => {4 const [users, setUsers] = useState([])5 const [isLoading, setIsLoading] = useState(false)67 const fetchData = () => {8 setIsLoading(true)9 fetch("https://jsonplaceholder.typicode.com/users")10 .then(response => {11 return response.json()12 })13 .then(data => {14 setIsLoading(false)15 setUsers(data)16 })17 }1819 useEffect(() => {20 fetchData()21 }, [])2223 return (24 <div>25 {isLoading && <p>Loading...</p>}26 {users.length > 0 && (27 <ul>28 {users.map(user => (29 <li key={user.id}>{user.name}</li>30 ))}31 </ul>32 )}33 </div>34 )35}3637export default LoadingText
Here we have used the && short circuit operator to display the loading text to conditionally render it. In my previous article, I have explained different ways to render react components conditionally.
Error handling while fetching data
While relying on external data, we should always have error handling in place. An API might fail because of any issues in the server or due to incorrect information passed from the client-side.
We will see how to handle errors in both then
syntax as well as async-await syntax.
Error handling in then() callback
We will update our endpoint to a non existent URL, so that it returns a HTTP 404 error.
1import React, { useEffect, useState } from "react"23const ErrorThen = () => {4 const [users, setUsers] = useState([])56 const fetchData = () => {7 fetch("https://jsonplaceholder.typicode.com/404")8 .then(response => {9 return response.json()10 })11 .then(data => {12 setUsers(data)13 })14 }1516 useEffect(() => {17 fetchData()18 }, [])1920 return (21 <div>22 {users.length > 0 && (23 <ul>24 {users.map(user => (25 <li key={user.id}>{user.name}</li>26 ))}27 </ul>28 )}29 </div>30 )31}3233export default ErrorThen
Now if you run the code, you will get an error: Unhandled Rejection (TypeError): Failed to fetch
We can fix this by checking if the response has a HTTP 2XX response code or not and if the server responds with anything other than 2XX, then we will throw an error and handle it in the catch method callback:
1import React, { useEffect, useState } from "react"23const ErrorThen = () => {4 const [users, setUsers] = useState([])5 const [error, setError] = useState("")67 const fetchData = () => {8 setError("")9 fetch("https://jsonplaceholder.typicode.com/404")10 .then(response => {11 // If the HTTP response is 2xx then it response.ok will have a value of true12 if (response.ok) {13 return response.json()14 } else {15 // If the API responds meaningful error message,16 // then you can get it by calling response.statusText17 throw new Error("Sorry something went wrong")18 }19 })20 .then(data => {21 setUsers(data)22 })23 .catch(error => {24 // It is always recommended to define the error messages25 // in the client side rather than simply relying on the server messages,26 // since server messages might not make sense to end user most of the time.27 setError(error.message)28 })29 }3031 useEffect(() => {32 fetchData()33 }, [])3435 return (36 <div>37 {error && <p>{error}</p>}38 {users.length > 0 && (39 <ul>40 {users.map(user => (41 <li key={user.id}>{user.name}</li>42 ))}43 </ul>44 )}45 </div>46 )47}4849export default ErrorThen
Also, note that if any error other than 4xx or 5xx error, such as network error happens,
then it will directly go to catch
callback without going to the first then
callback.
Error handling in async-await
To handle errors while using async-await syntax, we can go for the traditional try-catch blocks:
1import React, { useEffect, useState } from "react"23const ErrorAsyncAwait = () => {4 const [users, setUsers] = useState([])5 const [error, setError] = useState("")67 const fetchData = async () => {8 setError("")9 try {10 const response = await fetch("https://jsonplaceholder.typicode.com/404")11 if (!response.ok) {12 // If the API responds meaningful error message,13 // then you can get it by calling response.statusText14 throw new Error("Sorry something went wrong")15 }16 const data = await response.json()17 setUsers(data)18 } catch (error) {19 // It is always recommended to define the error messages20 // in the client side rather than simply relying on the server messages,21 // since server messages might not make sense to end user most of the time.22 setError(error.message)23 }24 }2526 useEffect(() => {27 fetchData()28 }, [])2930 return (31 <div>32 {error && <p>{error}</p>}33 {users.length > 0 && (34 <ul>35 {users.map(user => (36 <li key={user.id}>{user.name}</li>37 ))}38 </ul>39 )}40 </div>41 )42}4344export default ErrorAsyncAwait
Fetching data in React using Axios
We can make use of libraries like axios as well for fetching data.
The advantage of using axios is, it has additional features compared to fetch
like canceling previous requests.
First, let's install axios in our project by running the following command:
1yarn add axios
You can use
npm i axios
if you are using npm instead of yarn (if you have apackage-lock.json
file instead ofyarn.lock
).
Now we can use axios to fetch data as follows:
1import axios from "axios"2import React, { useEffect, useState } from "react"34const UsingAxios = () => {5 const [users, setUsers] = useState([])67 const fetchData = () => {8 axios.get("https://jsonplaceholder.typicode.com/users").then(response => {9 setUsers(response.data)10 })11 }1213 useEffect(() => {14 fetchData()15 }, [])1617 return (18 <div>19 {users.length > 0 && (20 <ul>21 {users.map(user => (22 <li key={user.id}>{user.name}</li>23 ))}24 </ul>25 )}26 </div>27 )28}2930export default UsingAxios
Note that we do not need 2 then blocks here since axios will handle converting response to JSON for us.
The response data can be accessed via response.data
.
Also, we do not have to check for response.ok
as in the case of fetch since all the errors will come to the catch method callback:
1const fetchData = () => {2 axios3 .get("https://jsonplaceholder.typicode.com/users")4 .then(response => {5 setUsers(response.data)6 })7 .catch(error => {8 console.log({ error })9 // Handle error10 })11}
There are many other features in axios, which you can read here.
Data fetching using Higher-Order Components (HOC)
If you want to separate code and data fetching into 2 different components, you can do so by extracting data fetching into an HOC:
1import axios from "axios"2import React, { useEffect, useState } from "react"34const withFetching = url => Component => {5 return () => {6 const [users, setUsers] = useState([])7 const [error, setError] = useState("")8 const [isLoading, setIsLoading] = useState(false)910 const fetchData = () => {11 setIsLoading(true)12 axios13 .get(url)14 .then(response => {15 setUsers(response.data)16 setIsLoading(false)17 })18 .catch(error => {19 setError("Sorry, something went wrong")20 setIsLoading(false)21 })22 }2324 useEffect(() => {25 fetchData()26 }, [])2728 return <Component users={users} error={error} isLoading={isLoading} />29 }30}3132export default withFetching
Now use the HOC created above while exporting the component:
1import React from "react"2import withFetching from "./withFetching"3const url = "https://jsonplaceholder.typicode.com/users"45const UsingHoc = ({ isLoading, error, users }) => {6 if (isLoading) {7 return <div>Loading..</div>8 }9 if (error) {10 return <div>{error}</div>11 }12 return (13 <div>14 {users.length > 0 && (15 <ul>16 {users.map(user => (17 <li key={user.id}>{user.name}</li>18 ))}19 </ul>20 )}21 </div>22 )23}2425export default withFetching(url)(UsingHoc)
Fetching data using custom hook
Fetching data using a custom hook is very similar to that of Higher-Order Component.
Let's first create a custom hook called useFetch
hook:
1import axios from "axios"2import { useEffect, useState } from "react"34const useFetch = url => {5 const [users, setUsers] = useState([])6 const [error, setError] = useState("")7 const [isLoading, setIsLoading] = useState(false)89 useEffect(() => {10 setIsLoading(true)11 axios12 .get(url)13 .then(response => {14 setUsers(response.data)15 setIsLoading(false)16 })17 .catch(error => {18 setError("Sorry, something went wrong")19 setIsLoading(false)20 })21 }, [url])2223 return { users, error, isLoading }24}2526export default useFetch
We can use this hook like how we use other hooks:
1import React from "react"2import useFetch from "./useFetch"3const url = "https://jsonplaceholder.typicode.com/users"45const UsingCustomHook = () => {6 const { users, error, isLoading } = useFetch(url)78 if (isLoading) {9 return <div>Loading..</div>10 }11 if (error) {12 return <div>{error}</div>13 }14 return (15 <div>16 {users.length > 0 && (17 <ul>18 {users.map(user => (19 <li key={user.id}>{user.name}</li>20 ))}21 </ul>22 )}23 </div>24 )25}2627export default UsingCustomHook
Fetching data using render props
One more alternative way for HOC is to use render props:
1import axios from "axios"2import { useEffect, useState } from "react"34const Fetcher = ({ url, children }) => {5 const [users, setUsers] = useState([])6 const [error, setError] = useState("")7 const [isLoading, setIsLoading] = useState(false)89 useEffect(() => {10 setIsLoading(true)11 axios12 .get(url)13 .then(response => {14 setUsers(response.data)15 setIsLoading(false)16 })17 .catch(error => {18 setError("Sorry, something went wrong")19 setIsLoading(false)20 })21 }, [url])2223 return children({ users, error, isLoading })24}2526export default Fetcher
In the above render prop function, we pass the local states to the children component and
we wrap our component with the Fetcher
component as shown below:
1import React from "react"2import Fetcher from "./Fetcher"3const url = "https://jsonplaceholder.typicode.com/users"45const UsingRenderProps = () => {6 return (7 <Fetcher url={url}>8 {({ isLoading, error, users }) => {9 if (isLoading) {10 return <div>Loading..</div>11 }12 if (error) {13 return <div>{error}</div>14 }15 return (16 <div>17 {users.length > 0 && (18 <ul>19 {users.map(user => (20 <li key={user.id}>{user.name}</li>21 ))}22 </ul>23 )}24 </div>25 )26 }}27 </Fetcher>28 )29}3031export default UsingRenderProps
Source code and Demo
You can view the complete source code here and a demo here.
Do follow me on twitter where I post developer insights more often!
Leave a Comment