Estoy tratando de crear una variable de estado de matriz llamada usersInfos la que puedo agregar cada vez que hago una llamada API, pero useEffect se actualiza con el valor inicial de la variable de estado userData .
// 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> ) }Supongo que tiene algo que ver con la actualización por lotes del estado, pero no estoy seguro. ¿Cuál sería la mejor forma práctica de hacer esto? Estoy iniciando sesión en la consola con el botón en la parte inferior, después de que se haya cargado la página.
Cuando userData se inicializa en () , obtengo el registro de la consola: (2) [undefined, {…}]
Cuando userData se inicializa en (0) , obtengo el registro de la consola: (2) [0, {…}] .
Cuando userData se inicializa en ([]) , obtengo el registro de la consola: (2) [Array(0), {…}]
Gracias
Cuando el segundo efecto se ejecuta por primera vez, los datos de userData no están definidos porque la solicitud de red aún no se ha resuelto. Simplemente podría ejecutar el efecto solo cuando userData no esté indefinido.
useEffect(() => { if (userData) { const newInfos = [ ...usersInfos, userData, ] setUsersInfos(newInfos); } }, [userData])Sin embargo, si el código presentado en la pregunta está casi completo, usaría el siguiente código en su lugar. De esta forma puedo actualizar ambos estados con un solo efecto.
El siguiente código es un fragmento del ejemplo completamente funcional de CodeSandbox .
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> ); }Si intenta registrar userInfos justo después del setter, registrará el estado anterior porque la función dentro del efecto es un cierre y captura el valor del estado cuando se ejecuta el efecto. Dado que el estado de React es inmutable, la actualización del estado no funciona como una asignación, pero se actualizará en el próximo renderizado.