Como han señalado otros, el problema es que useStatesolo 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 setIntervalque haga tictac, realmente llamará setTime(time + 1), pero timesiempre mantendrá el valor que tenía inicialmente cuando setIntervalse 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 useIntervalgancho 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 = nully 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 delaycuando 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 setTimeoutlugar de setInterval, consulte esto: https://stackoverflow.com/a/59274757/3723993 .
También puede encontrar una versión declarativa de setTimeouty setInterval, useTimeouty useInterval, además de un useThrottledCallbackgancho personalizado escrito en TypeScript en https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .