Tengo una aplicación de reacción con dos botones, que al hacer clic cargan el nombre de usuario del servidor. El comportamiento funciona si hago clic en los botones uno a la vez y espero la respuesta; sin embargo, si hago clic en ambos, la respuesta de la API para el segundo botón escribe un valor en el estado que está obsoleto debido a que el primer botón se atasca en el estado de carga. ¿Cómo puedo resolver esto para tener siempre los datos más recientes cuando se resuelve la promesa?
Demostración de espacio aislado de código: https://codesandbox.io/s/pensive-frost-qkm9xh?file=/src/App.js:0-1532
import "./styles.css"; import LoadingButton from "@mui/lab/LoadingButton"; import { useRef, useState } from "react"; import { Typography } from "@mui/material"; const getUsersApi = (id) => { const users = { "12": "John", "47": "Paul", "55": "Alice" }; return new Promise((resolve) => { setTimeout((_) => { resolve(users[id]); }, 1000); }); }; export default function App() { const [users, setUsers] = useState({}); const availableUserIds = [12, 47]; const loadUser = (userId) => { // Mark button as loading const updatedUsers = { ...users }; updatedUsers[userId] = { id: userId, name: undefined, isLoading: true, isFailed: false }; setUsers(updatedUsers); // Call API getUsersApi(userId).then((userName) => { // Update state with user name const updatedUsers = { ...users }; updatedUsers[userId] = { ...updatedUsers[userId], name: userName, isLoading: false, isFailed: false }; setUsers(updatedUsers); }); }; return ( <div className="App"> {availableUserIds.map((userId) => users[userId]?.name ? ( <Typography variant="h3">{users[userId].name}</Typography> ) : ( <LoadingButton key={userId} loading={users[userId]?.isLoading} variant="outlined" onClick={() => loadUser(userId)} > Load User {userId} </LoadingButton> ) )} </div> ); }El problema es que el setter de useState es asíncrono, por lo tanto, en su función de cargador, cuando define const updatedUsers = { ...users }; , el usuario no es necesario actualizar.
Afortunadamente, el setter de useState nos permite acceder al estado anterior. Si refactoriza su código de esta manera, debería funcionar:
const loadUser = (userId) => { // Mark button as loading const updatedUsers = { ...users }; updatedUsers[userId] = { id: userId, name: undefined, isLoading: true, isFailed: false }; setUsers(updatedUsers); // Call API getUsersApi(userId).then((userName) => { // Update state with user name setUsers(prevUsers => { const updatedUsers = { ...prevUsers }; updatedUsers[userId] = { ...updatedUsers[userId], name: userName, isLoading: false, isFailed: false }; return updatedUsers }); }); };Aquí un patio de juegos de React con una versión de trabajo simplificada.