Reputation: 3854
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
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.
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.
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.
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
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
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
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