Fetch and display data from API in React js
Tags: react, fetch, axios, api
Published: 2022-07-16 15:33:02

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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) Web API, which can be used to fetch data from APIs.
In this tutorial, we will be fetching data from the [JSON Server](https://github.com/typicode/json-server) APIs.

```jsx
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 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](https://www.codingdeft.com/posts/react-useeffect-hook/)
> and [how to loop through an array in React](https://www.codingdeft.com/posts/react-for-loop/) from my previous articles.

Since the API calls are asynchronous, `fetch` API returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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:

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

```jsx
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`

![useEffect async warning](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95yiixsojkneaonvdud3.png)

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

```jsx
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](https://www.codingdeft.com/posts/react-onclick-event-with-examples/) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) or string interpolation in JavaScript.

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

```jsx
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](https://www.codingdeft.com/posts/react-onblur-onchange/#onchange-and-onblur-events-in-react).

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

```jsx
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](https://www.codingdeft.com/posts/react-conditional-rendering/).

## 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.

```jsx
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`

![error failed to fetch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4y2px6ysocippgl42fke.png) 

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:

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

```jsx
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](https://www.npmjs.com/package/axios) as well for fetching data. The advantage of using axios is, it has additional features compared to `fetch` like [canceling previous requests](https://www.codingdeft.com/posts/axios-cancel-previous-request-react/).

First, let's install axios in our project by running the following command:

```bash
yarn 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:

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

```js
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](https://axios-http.com/docs/intro#features).

## 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](https://reactjs.org/docs/higher-order-components.html):

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

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

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

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

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

```jsx
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](https://github.com/collegewap/react-fetch-data) and a [demo here](https://react-fetch-data.vercel.app/).