Tengo un componente que representa una tabla con objetos. Este componente muestra un botón que, cuando se presiona, envía al padre un objeto específico. El padre debe configurarlo en el estado para mostrar algunas cosas gráficas. La representación funciona correctamente, lo que no entiendo es por qué obtengo un valor desactualizado después de configurar el estado correctamente. No es una condición de carrera , React simplemente ignora el valor actualizado de una variable, incluso cuando vuelve a representar el componente correctamente.
Un ejemplo mínimo:
import { useState } from "react"; import { SomeComponent } from "./SomeComponent"; export default function App() { const [currentID, setCurrentID] = useState(null); function getData() { console.log("Getting data of: ", currentID); // PROBLEM: this is null } function setAndRetrieveData(value) { setCurrentID(value); // Just to show the problem and discard race conditions. setTimeout(() => { getData(); }, 1500); } return ( <div className="App"> <h1>Current ID: {currentID}</h1> {/* This works fine */} <SomeComponent getInfoFor={setAndRetrieveData} /> </div> ); }AlgúnComponente:
export function SomeComponent(props) { const randomID = 45; return <button onClick={() => props.getInfoFor(randomID)}>Get info</button>; }Incluso con soluciones como useStateCallback , el problema persiste.
¿Hay alguna manera de hacer esto sin tener que usar el horrible useEffect que no está claro al leer el código? Debido a que la lógica del sistema es "cuando se presione este botón, haga una solicitud para obtener la información", usando el gancho useEffect la lógica se convierte en "cuando cambie el valor de currentID haga una solicitud", si en algún momento quiero cambiar el estado de esa variable y realizar otra acción que no sea obtener los datos del servidor entonces estaré en problemas.
Gracias por adelantado
Creo que este es un problema con la forma en que funcionan los cierres de Javascript.
Cuando ejecuta una función, se empaqueta con todos los datos que le pertenecen y luego se ejecuta.
El problema es que llamas a esto:
setTimeout(() => { getData(); }, 1500); dentro setAndRetrieveData(value) .
Aunque está dentro de setTimeout, la función getData() se ha incluido con la información que necesita ( currentID ) en ese momento, no cuando realmente se ejecuta. Por lo tanto, se incluye con el ID actual antes de que se realice la actualización de estado.
Desafortunadamente, recomendaría usar useEffect. Esta es la mejor manera de asegurarse de evitar problemas como este y cualquier posible condición de carrera. ¡Esperemos que alguien más pueda proporcionar un enfoque diferente!
cuando se llama a setAndRetrieveData , establece un estado que hace que el componente se vuelva a representar para reflejar el nuevo estado. Cuando finaliza el tiempo de espera La función getData se creó en el render anterior. Y, por lo tanto, solo tiene acceso a la variable de estado del renderizado anterior. Eso ahora es indefinido.
lo que podría intentar es usar un gancho useEffect que escuche los cambios de currentID.
useEffect(() => { const timeoutId = setTimeout(() => { // Do something with the updated value },1000); return () => { // if the data updates prematurely // we cancel the timeout and start a new one clearTimeout(timeoutId); } },[currentID])