DEV Community

Cover image for Fetch and display data from API in React js
collegewap
collegewap

Posted on • Updated on • Originally published at codingdeft.com

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.

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
Enter fullscreen mode Exit fullscreen mode

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 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:

const fetchData = () =>
  fetch("https://jsonplaceholder.typicode.com/users")
    .then(response => response.json())
    .then(data => setUsers(data))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

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
Enter fullscreen mode Exit fullscreen mode

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
    })
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Source code and Demo

You can view the complete source code here and a demo here.

Oldest comments (2)

Collapse
 
ecyrbe profile image
ecyrbe

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)

Collapse
 
collegewap profile image
collegewap

LOL, may be you should revisit some of the docs:

beta.reactjs.org/learn/synchronizi...

reactjs.org/docs/faq-ajax.html