Mehdi Faraji
Mehdi Faraji

Reputation: 3854

How can display data fetched from an api properly in react?

I'm trying to display an array of users from jsonplaceholder api :

function App() {
    const [ data, setData ] = React.useState([]);
    React.useEffect(
        () => {
            axios
                .get('https://jsonplaceholder.typicode.com/users')
                .then((res) => {
                    data.push(res.data);
                    console.log(data);
                })
                .catch((err) => console.log(err));
        },
        [ data ]
    );
    return (
        <div className="App">
            {data.map((item) => {
                return <div>{item.name}</div>;
            })}
        </div>
    );
}

I get the console log which tells me data is fetched successfully and the data array is full of objects :

0: (10) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
length: 1
__proto__: Array(0)

But for some reason the data is not diplayed on the screen and It doesn't throw an error or somewhat so I can fix it . How can I fetch and display data in a react component properly ?

Upvotes: 1

Views: 1212

Answers (4)

Bens Steves
Bens Steves

Reputation: 2849

React state is immutable. You do not want to mutate the state variable directly by pushing to the array. Instead, you want to use your setState function that will update your data state variable.

Understanding useEffect

When you pass a second arguement to the useEffect, in this case data it will update every time the data variable changes causing an infinite loop since every call will update the state.

You can trigger this API request on mount by not passing anything to the useEffect. And then cleanup the API request to prevent possible memory leaks.

Refactor: Pass 1

Cleaning up the useEffect and adding a key attribute to your returned JSX in the map (otherwise you get a warning in the console saying you need to provide a key).

import React, { useEffect, useState } from 'react'
function App() {
    const [ data, setData ] = useState([]);
    
    useEffect(() => {
      axios
        .get('https://jsonplaceholder.typicode.com/users')
        .then((res) => {
           setData(res.data)
         })
         .catch((err) => console.log(err));
        },[]);

    return (
        <div className="App">
            {data.map((item) => {
                return <div key={item.name}>{item.name}</div>; //you want to use a unique key, name may not be unique so use something unique here
            })}
        </div>
    );
}

That should work fine. There is still room for improvement.

Refactor: Pass 2

We want to prevent potential memory leaks in our application so we should add a cleanup to that useEffect. A cleanup tells the useEffect what to do when the component is unmounting. Here, we want to end the API request or cancel it if we unmount before it is finished otherwise it will finish and try to update a state variable that no longer exists.

import React, { useEffect, useState } from 'react'
function App() {
    const [ data, setData ] = useState([]);
    
    useEffect(() => {
      const axiosSource = axios.CancelToken.source()
      const fetchUsers = async () => { 
       try {
         await axios.get(url, { cancelToken: axiosSource.token })
             // update state        
       } catch(error){ //handle error }
      }
      fetchUser();
      return () => { 
        axiosSource.cancel()
      }
    },[]);

    return (
        <div className="App">
            {data.map((item) => {
                return <div key={item.name}>{item.name}</div>;
            })}
        </div>
    );
}

Upvotes: 1

Akshay Kumar
Akshay Kumar

Reputation: 1035

As others have suggested, you need to update the component state data using setData function. States and components have a reload behaviour. You can't modify the value of existing state. You can only supply a new one. When you supply a new state, the component reloads (re-renders in react terminology) using the new state. So since you have an initial state ([] or empty array) the component is first rendered with that state.

Once the fetch inside the useEffect completes execution, and you have the new data. You have to tell react to create a new state and rerender the component as per the value of the new state. That's what you do when you use the setData function.

You will have to create the new state's shape each time you want to change something depending on whether you want to replace the old data completely with the new data, or you want to keep only unique values. But that's probably out of the scope of this question.

The question you want to ask yourself is: Is the data you are getting from the API ready to be used in the component or do you need to perform some sort of sanitization before passing it as the new state?

Upvotes: 1

subashMahapatra
subashMahapatra

Reputation: 6857

You have to update the state in an immutable way. When you call data.push(res.data); it will mutate the existing data array and return the new length of the data array.

The solution is to use the setData function instead to update the state.

React.useEffect(
        () => {
            axios
                .get('https://jsonplaceholder.typicode.com/users')
                .then((res) => {
                    setData(res.data)
                })
                .catch((err) => console.log(err));
        },
        []  /* The dependency array should be empty */
    );

Another issue is in the second argument to React.useEffect you are providing data as a dependency which means every time data changes the effect callback will be executed again, resulting in a nasty infinite loop of making the API request over and over. The dependency array should be empty so the effect callback is executed only once when the component mounts resulting in a similar behaviour of componentDidMount lifecycle method of class based components.

Upvotes: 2

Michael Ceber
Michael Ceber

Reputation: 2452

OK you are not calling setData anywhere in that code.

When ever you want to update the value of 'data' this must be done by making your modifications then calling setData(data). This will cause the component to rerender.

Upvotes: 1

Related Questions