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.
Fetching data using inbuilt fetch
API.
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.
import React, { useEffect, useState } from "react"
const UsingFetch = () => {
const [users, setUsers] = useState([])
const fetchData = () => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
return response.json()
})
.then(data => {
setUsers(data)
})
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export default UsingFetch
In the above code,
- We have 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:
const fetchData = () =>
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.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:
import React, { useEffect, useState } from "react"
const AsyncAwait = () => {
const [users, setUsers] = useState([])
const fetchData = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users")
const data = await response.json()
setUsers(data)
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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:
import React, { useState } from "react"
const ButtonClick = () => {
const [users, setUsers] = useState([])
const fetchData = () => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
return response.json()
})
.then(data => {
setUsers(data)
})
}
return (
<div>
<button onClick={fetchData}>Fetch Users</button>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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.
import React, { useEffect, useState } from "react"
const PassParam = () => {
const [user, setUser] = useState([])
const id = 1
const fetchData = () => {
fetch(`https://jsonplaceholder.typicode.com/users?id=${id}`)
.then(response => {
return response.json()
})
.then(data => {
setUser(data[0].name)
})
}
useEffect(() => {
fetchData()
}, [])
return <div>Name: {user}</div>
}
export 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:
import React, { useState } from "react"
const SearchUser = () => {
const [users, setUsers] = useState([])
const fetchData = e => {
const query = e.target.value
fetch(`https://jsonplaceholder.typicode.com/users?q=${query}`)
.then(response => {
return response.json()
})
.then(data => {
setUsers(data)
})
}
return (
<div>
<input onChange={fetchData} label="Search User" />
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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 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.
import React, { useEffect, useState } from "react"
const LoadingText = () => {
const [users, setUsers] = useState([])
const [isLoading, setIsLoading] = useState(false)
const fetchData = () => {
setIsLoading(true)
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => {
return response.json()
})
.then(data => {
setIsLoading(false)
setUsers(data)
})
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{isLoading && <p>Loading...</p>}
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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.
import React, { useEffect, useState } from "react"
const ErrorThen = () => {
const [users, setUsers] = useState([])
const fetchData = () => {
fetch("https://jsonplaceholder.typicode.com/404")
.then(response => {
return response.json()
})
.then(data => {
setUsers(data)
})
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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:
import React, { useEffect, useState } from "react"
const ErrorThen = () => {
const [users, setUsers] = useState([])
const [error, setError] = useState("")
const fetchData = () => {
setError("")
fetch("https://jsonplaceholder.typicode.com/404")
.then(response => {
// If the HTTP response is 2xx then it response.ok will have a value of true
if (response.ok) {
return response.json()
} else {
// If the API responds meaningful error message,
// then you can get it by calling response.statusText
throw new Error("Sorry something went wrong")
}
})
.then(data => {
setUsers(data)
})
.catch(error => {
// It is always recommended to define the error messages
// in the client side rather than simply relying on the server messages,
// since server messages might not make sense to end user most of the time.
setError(error.message)
})
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{error && <p>{error}</p>}
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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:
import React, { useEffect, useState } from "react"
const ErrorAsyncAwait = () => {
const [users, setUsers] = useState([])
const [error, setError] = useState("")
const fetchData = async () => {
setError("")
try {
const response = await fetch("https://jsonplaceholder.typicode.com/404")
if (!response.ok) {
// If the API responds meaningful error message,
// then you can get it by calling response.statusText
throw new Error("Sorry something went wrong")
}
const data = await response.json()
setUsers(data)
} catch (error) {
// It is always recommended to define the error messages
// in the client side rather than simply relying on the server messages,
// since server messages might not make sense to end user most of the time.
setError(error.message)
}
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{error && <p>{error}</p>}
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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:
yarn 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:
import axios from "axios"
import React, { useEffect, useState } from "react"
const UsingAxios = () => {
const [users, setUsers] = useState([])
const fetchData = () => {
axios.get("https://jsonplaceholder.typicode.com/users").then(response => {
setUsers(response.data)
})
}
useEffect(() => {
fetchData()
}, [])
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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:
const fetchData = () => {
axios
.get("https://jsonplaceholder.typicode.com/users")
.then(response => {
setUsers(response.data)
})
.catch(error => {
console.log({ error })
// Handle error
})
}
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:
import axios from "axios"
import React, { useEffect, useState } from "react"
const withFetching = url => Component => {
return () => {
const [users, setUsers] = useState([])
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
const fetchData = () => {
setIsLoading(true)
axios
.get(url)
.then(response => {
setUsers(response.data)
setIsLoading(false)
})
.catch(error => {
setError("Sorry, something went wrong")
setIsLoading(false)
})
}
useEffect(() => {
fetchData()
}, [])
return <Component users={users} error={error} isLoading={isLoading} />
}
}
export default withFetching
Now use the HOC created above while exporting the component:
import React from "react"
import withFetching from "./withFetching"
const url = "https://jsonplaceholder.typicode.com/users"
const UsingHoc = ({ isLoading, error, users }) => {
if (isLoading) {
return <div>Loading..</div>
}
if (error) {
return <div>{error}</div>
}
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export 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:
import axios from "axios"
import { useEffect, useState } from "react"
const useFetch = url => {
const [users, setUsers] = useState([])
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
setIsLoading(true)
axios
.get(url)
.then(response => {
setUsers(response.data)
setIsLoading(false)
})
.catch(error => {
setError("Sorry, something went wrong")
setIsLoading(false)
})
}, [url])
return { users, error, isLoading }
}
export default useFetch
We can use this hook like how we use other hooks:
import React from "react"
import useFetch from "./useFetch"
const url = "https://jsonplaceholder.typicode.com/users"
const UsingCustomHook = () => {
const { users, error, isLoading } = useFetch(url)
if (isLoading) {
return <div>Loading..</div>
}
if (error) {
return <div>{error}</div>
}
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}
export default UsingCustomHook
Fetching data using render props
One more alternative way for HOC is to use render props:
import axios from "axios"
import { useEffect, useState } from "react"
const Fetcher = ({ url, children }) => {
const [users, setUsers] = useState([])
const [error, setError] = useState("")
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
setIsLoading(true)
axios
.get(url)
.then(response => {
setUsers(response.data)
setIsLoading(false)
})
.catch(error => {
setError("Sorry, something went wrong")
setIsLoading(false)
})
}, [url])
return children({ users, error, isLoading })
}
export 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:
import React from "react"
import Fetcher from "./Fetcher"
const url = "https://jsonplaceholder.typicode.com/users"
const UsingRenderProps = () => {
return (
<Fetcher url={url}>
{({ isLoading, error, users }) => {
if (isLoading) {
return <div>Loading..</div>
}
if (error) {
return <div>{error}</div>
}
return (
<div>
{users.length > 0 && (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
)
}}
</Fetcher>
)
}
export default UsingRenderProps
Source code and Demo
You can view the complete source code here and a demo here.
Top comments (2)
Since react 18, using useEffect for data fetching should no more be an option (since now there is no guaranty it will not be called more than once).
Instead i highly suggest anyone to not reinvent the wheel and use an external data fetching hook library (react-query, swr, redux-toolkit rtk-query, @zodios/react, etc)
LOL, may be you should revisit some of the docs:
beta.reactjs.org/learn/synchronizi...
reactjs.org/docs/faq-ajax.html