Skip to content
react

Fetch and display data from API in React js

Apr 1, 2023Abhishek EH13 Min Read
Fetch and display data from API in React js

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.

1import React, { useEffect, useState } from "react"
2
3const UsingFetch = () => {
4 const [users, setUsers] = useState([])
5
6 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 }
15
16 useEffect(() => {
17 fetchData()
18 }, [])
19
20 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}
32
33export 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 the useEffect hook, we are calling fetchData 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"
2
3const AsyncAwait = () => {
4 const [users, setUsers] = useState([])
5
6 const fetchData = async () => {
7 const response = await fetch("https://jsonplaceholder.typicode.com/users")
8 const data = await response.json()
9 setUsers(data)
10 }
11
12 useEffect(() => {
13 fetchData()
14 }, [])
15
16 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}
28
29export 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

useEffect async warning

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"
2
3const ButtonClick = () => {
4 const [users, setUsers] = useState([])
5
6 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 }
15
16 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}
29
30export 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"
2
3const PassParam = () => {
4 const [user, setUser] = useState([])
5 const id = 1
6
7 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 }
16
17 useEffect(() => {
18 fetchData()
19 }, [])
20
21 return <div>Name: {user}</div>
22}
23
24export 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"
2
3const SearchUser = () => {
4 const [users, setUsers] = useState([])
5
6 const fetchData = e => {
7 const query = e.target.value
8 fetch(`https://jsonplaceholder.typicode.com/users?q=${query}`)
9 .then(response => {
10 return response.json()
11 })
12 .then(data => {
13 setUsers(data)
14 })
15 }
16
17 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}
30
31export 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"
2
3const LoadingText = () => {
4 const [users, setUsers] = useState([])
5 const [isLoading, setIsLoading] = useState(false)
6
7 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 }
18
19 useEffect(() => {
20 fetchData()
21 }, [])
22
23 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}
36
37export 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"
2
3const ErrorThen = () => {
4 const [users, setUsers] = useState([])
5
6 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 }
15
16 useEffect(() => {
17 fetchData()
18 }, [])
19
20 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}
32
33export default ErrorThen

Now if you run the code, you will get an error: Unhandled Rejection (TypeError): Failed to fetch

error 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"
2
3const ErrorThen = () => {
4 const [users, setUsers] = useState([])
5 const [error, setError] = useState("")
6
7 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 true
12 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.statusText
17 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 messages
25 // 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 }
30
31 useEffect(() => {
32 fetchData()
33 }, [])
34
35 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}
48
49export 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"
2
3const ErrorAsyncAwait = () => {
4 const [users, setUsers] = useState([])
5 const [error, setError] = useState("")
6
7 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.statusText
14 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 messages
20 // 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 }
25
26 useEffect(() => {
27 fetchData()
28 }, [])
29
30 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}
43
44export 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 a package-lock.json file instead of yarn.lock).

Now we can use axios to fetch data as follows:

1import axios from "axios"
2import React, { useEffect, useState } from "react"
3
4const UsingAxios = () => {
5 const [users, setUsers] = useState([])
6
7 const fetchData = () => {
8 axios.get("https://jsonplaceholder.typicode.com/users").then(response => {
9 setUsers(response.data)
10 })
11 }
12
13 useEffect(() => {
14 fetchData()
15 }, [])
16
17 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}
29
30export 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 axios
3 .get("https://jsonplaceholder.typicode.com/users")
4 .then(response => {
5 setUsers(response.data)
6 })
7 .catch(error => {
8 console.log({ error })
9 // Handle error
10 })
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:

withFetching.js
1import axios from "axios"
2import React, { useEffect, useState } from "react"
3
4const withFetching = url => Component => {
5 return () => {
6 const [users, setUsers] = useState([])
7 const [error, setError] = useState("")
8 const [isLoading, setIsLoading] = useState(false)
9
10 const fetchData = () => {
11 setIsLoading(true)
12 axios
13 .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 }
23
24 useEffect(() => {
25 fetchData()
26 }, [])
27
28 return <Component users={users} error={error} isLoading={isLoading} />
29 }
30}
31
32export 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"
4
5const 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}
24
25export 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"
3
4const useFetch = url => {
5 const [users, setUsers] = useState([])
6 const [error, setError] = useState("")
7 const [isLoading, setIsLoading] = useState(false)
8
9 useEffect(() => {
10 setIsLoading(true)
11 axios
12 .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])
22
23 return { users, error, isLoading }
24}
25
26export 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"
4
5const UsingCustomHook = () => {
6 const { users, error, isLoading } = useFetch(url)
7
8 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}
26
27export default UsingCustomHook

Fetching data using render props

One more alternative way for HOC is to use render props:

Fetcher.js
1import axios from "axios"
2import { useEffect, useState } from "react"
3
4const Fetcher = ({ url, children }) => {
5 const [users, setUsers] = useState([])
6 const [error, setError] = useState("")
7 const [isLoading, setIsLoading] = useState(false)
8
9 useEffect(() => {
10 setIsLoading(true)
11 axios
12 .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])
22
23 return children({ users, error, isLoading })
24}
25
26export 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"
4
5const 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}
30
31export 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!

© 2024 CodingDeft.Com