Como han señalado otros, el problema es que useState
solo se llama una vez (ya que es deps = []
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)
, pero time
siempre mantendrá el valor que tenía inicialmente cuando setInterval
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 con setState
):
setTime(prevTime => prevTime + 1);
Pero lo animo a crear su propio useInterval
gancho para que pueda SECAR y simplificar su código mediante el uso setInterval
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);
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
React.useEffect(() => {
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(() => callbackRef.current(), delay);
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/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 de setInterval
, consulte esto: https://stackoverflow.com/a/59274757/3723993 .
También puede encontrar una versión declarativa de setTimeout
y setInterval
, useTimeout
y useInterval
, además de un useThrottledCallback
gancho personalizado escrito en TypeScript en https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .