'React useEffect updating with default initialized useState
I'm trying to create an array state variable called usersInfos that I can append to whenever I do an api call, but useEffect is updating with the initial value of the userData state variable.
// api.js
import axios from 'axios';
export function fetchUserData () {
return axios.get('https://randomuser.me/api')
.then(res => {
return res;
})
.catch(err => {
console.error(err);
})
}
import { fetchUserData } from '../../src/api';
import { useState, useEffect } from 'react';
import ProfileCard from './profilecard';
export default function UserProfile() {
const [userData, setUserData] = useState();
const [usersInfos, setUsersInfos] = useState([]);
const getUserData = async () => {
let ud = await fetchUserData()
setUserData(ud);
}
useEffect(() => {
getUserData();
}, [])
useEffect(() => {
const newInfos = [
...usersInfos,
userData,
]
setUsersInfos(newInfos);
console.log(usersInfos);
}, [userData])
return (
<div className='userprofile'>
<button onClick={() => { getUserData() }}> Fetch Random User </button>
<button onClick={() => { console.log(usersInfos) }}> log usersInfos </button>
{usersInfos.map((user, idx) => {
<ProfileCard userData={user} key={idx} />
})}
</div>
)
}
I'm assuming it's something to do with the state batch updating, but I'm not sure. What would be the best practice way of doing this? I am console logging with the button at the bottom, after the page has loaded.
When userData is initialized to (), I get console log: (2) [undefined, {…}]
When userData is initialized to (0), I get console log: (2) [0, {…}].
When userData is initialized to ([]), i get console log:(2) [Array(0), {…}]
Thanks
Solution 1:[1]
When the 2nd effect runs for the first time, userData is undefined because the network request isn't resolved yet. You could simply run the effect only when userData is not undefined.
useEffect(() => {
if (userData) {
const newInfos = [
...usersInfos,
userData,
]
setUsersInfos(newInfos);
}
}, [userData])
However, if the code presented in the question is almost complete, I would use the code below instead. In this way I can update both states with a single effect.
The code below is a snippet from the CodeSandbox fully functional example.
export default function UserProfile() {
const [userData, setUserData] = useState();
const [usersInfos, setUsersInfos] = useState([]);
const getUserData = useCallback(async () => {
let ud = await fetchUserData();
setUserData(ud);
setUsersInfos((prevInfos) => [...prevInfos, ud]);
}, []);
useEffect(() => {
getUserData();
}, [getUserData]);
return (
<div className="userprofile">
<button onClick={getUserData}>Fetch Random User</button>
<button
onClick={() => {
console.log(usersInfos);
}}
>
log usersInfos
</button>
{userData && (
<p>
Current user: {userData.name.first} {userData.name.last}
</p>
)}
<ul>
{usersInfos.map((user, idx) => (
<ProfileCard userData={user} key={idx} />
))}
</ul>
</div>
);
}
If you try to log userInfos just after the setter, it will log the previous state because the function inside the effect is a closure and it captures the value of the state when the effect runs. Since React state is immutable the state update does not works like an assignment but it will be updated at the next render.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
