Estoy probando los nuevos React Hooks y tengo un componente de reloj con un contador que se supone que aumenta cada segundo. Sin embargo, el valor no aumenta más allá de uno.
function Clock() {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const timer = window.setInterval(() => {
setTime(time + 1);
}, 1000);
return () => {
window.clearInterval(timer);
};
}, []);
return (
<div>Seconds: {time}</div>
);
}
ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
fuente
useEffect
La función se evalúa solo una vez en el montaje del componente cuando se proporciona una lista de entrada vacía.Una alternativa
setInterval
es establecer un nuevo intervalosetTimeout
cada vez que se actualiza el estado:const [time, setTime] = React.useState(0); React.useEffect(() => { const timer = setTimeout(() => { setTime(time + 1); }, 1000); return () => { clearTimeout(timer); }; }, [time]);
El impacto en el rendimiento de
setTimeout
es insignificante y generalmente se puede ignorar. A menos que el componente es sensible al tiempo hasta el punto en recién configurado tiempos de espera causan efectos indeseables, tantosetInterval
ysetTimeout
enfoques son aceptables.fuente
Como han señalado otros, el problema es que
useState
solo se llama una vez (ya que esdeps = []
para configurar el intervalo:React.useEffect(() => { const timer = window.setInterval(() => { setTime(time + 1); }, 1000); return () => window.clearInterval(timer); }, []);
Luego, cada vez
setInterval
que haga tictac, realmente llamarásetTime(time + 1)
, perotime
siempre mantendrá el valor que tenía inicialmente cuandosetInterval
se definió la devolución de llamada (cierre).Puede usar la forma alternativa de
useState
's setter y proporcionar una devolución de llamada en lugar del valor real que desea establecer (como consetState
):setTime(prevTime => prevTime + 1);
Pero lo animo a crear su propio
useInterval
gancho para que pueda SECAR y simplificar su código mediante el usosetInterval
declarativo , como Dan Abramov sugiere aquí en Cómo hacer un setInterval declarativo con React Hooks :function useInterval(callback, delay) { const intervalRef = React.useRef(); const callbackRef = React.useRef(callback); // Remember the latest callback: // // Without this, if you change the callback, when setInterval ticks again, it // will still call your old callback. // // If you add `callback` to useEffect's deps, it will work fine but the // interval will be reset. React.useEffect(() => { callbackRef.current = callback; }, [callback]); // Set up the interval: React.useEffect(() => { if (typeof delay === 'number') { intervalRef.current = window.setInterval(() => callbackRef.current(), delay); // Clear interval if the components is unmounted or the delay changes: return () => window.clearInterval(intervalRef.current); } }, [delay]); // Returns a ref to the interval ID in case you want to clear it manually: return intervalRef; } const Clock = () => { const [time, setTime] = React.useState(0); const [isPaused, setPaused] = React.useState(false); const intervalRef = useInterval(() => { if (time < 10) { setTime(time + 1); } else { window.clearInterval(intervalRef.current); } }, isPaused ? null : 1000); return (<React.Fragment> <button onClick={ () => setPaused(prevIsPaused => !prevIsPaused) } disabled={ time === 10 }> { isPaused ? 'RESUME ⏳' : 'PAUSE 🚧' } </button> <p>{ time.toString().padStart(2, '0') }/10 sec.</p> <p>setInterval { time === 10 ? 'stopped.' : 'running...' }</p> </React.Fragment>); } ReactDOM.render(<Clock />, document.querySelector('#app'));
body, button { font-family: monospace; } body, p { margin: 0; } p + p { margin-top: 8px; } #app { display: flex; flex-direction: column; align-items: center; min-height: 100vh; } button { margin: 32px 0; padding: 8px; border: 2px solid black; background: transparent; cursor: pointer; border-radius: 2px; }
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
Además de producir un código más simple y limpio, esto le permite pausar (y borrar) el intervalo automáticamente al pasar
delay = null
y también devuelve la ID del intervalo, en caso de que desee cancelarlo usted mismo manualmente (eso no está cubierto en las publicaciones de Dan).En realidad, esto también podría mejorarse para que no se reinicie
delay
cuando no se pausa, pero supongo que para la mayoría de los casos de uso esto es lo suficientemente bueno.Si está buscando una respuesta similar para en
setTimeout
lugar desetInterval
, consulte esto: https://stackoverflow.com/a/59274757/3723993 .También puede encontrar una versión declarativa de
setTimeout
ysetInterval
,useTimeout
yuseInterval
, además de unuseThrottledCallback
gancho personalizado escrito en TypeScript en https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .fuente
Una solución alternativa sería usar
useReducer
, ya que siempre se pasará al estado actual.function Clock() { const [time, dispatch] = React.useReducer((state = 0, action) => { if (action.type === 'add') return state + 1 return state }); React.useEffect(() => { const timer = window.setInterval(() => { dispatch({ type: 'add' }); }, 1000); return () => { window.clearInterval(timer); }; }, []); return ( <div>Seconds: {time}</div> ); } ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
fuente
useEffect
aquí se llama varias veces para actualizar la hora, mientras que la matriz de dependencias está vacía, lo que significa que solo seuseEffect
debe llamar la primera vez que se procesa el componente / aplicación?useEffect
se llama solo una vez, cuando el componente se procesa por primera vez. Pero dentro de ella, hay unasetInterval
que se encarga de cambiar la hora de forma regular. Te sugiero que leas un pocosetInterval
, ¡las cosas deberían estar más claras después de eso! developer.mozilla.org/en-US/docs/Web/API/…Haz lo siguiente, funciona bien.
const [count , setCount] = useState(0); async function increment(count,value) { await setCount(count => count + 1); } //call increment function increment(count);
fuente
count => count + 1
devolución de llamada fue lo que funcionó para mí, ¡gracias!Estas soluciones no funcionan para mí porque necesito obtener la variable y hacer algunas cosas, no solo actualizarla.
Obtengo una solución alternativa para obtener el valor actualizado del gancho con una promesa
P.ej:
async function getCurrentHookValue(setHookFunction) { return new Promise((resolve) => { setHookFunction(prev => { resolve(prev) return prev; }) }) }
Con esto puedo obtener el valor dentro de la función setInterval como esta
let dateFrom = await getCurrentHackValue(setSelectedDateFrom);
fuente
Dile a React que vuelva a renderizar cuando cambie el tiempo. optar por no
function Clock() { const [time, setTime] = React.useState(0); React.useEffect(() => { const timer = window.setInterval(() => { setTime(time + 1); }, 1000); return () => { window.clearInterval(timer); }; }, [time]); return ( <div>Seconds: {time}</div> ); } ReactDOM.render(<Clock />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script> <script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script> <div id="app"></div>
fuente
count
cambio.setTimeout()
se prefiere como lo señaló Estus