Le problème est que vous appelez setTimeout
à l'extérieur de useEffect
Vous définissez donc un nouveau délai d'attente à chaque fois que le composant est rendu, ce qui finira par être invoqué à nouveau et modifiera l'état, obligeant le composant à être rendu à nouveau, ce qui définira un nouveau délai d'attente, qui...
Donc, comme vous l'avez déjà découvert, la façon d'utiliser setTimeout
ou setInterval
avec des crochets est de les envelopper dans useEffect
comme ça :
React.useEffect(() => {
const timeoutID = window.setTimeout(() => {
...
}, 1000);
return () => window.clearTimeout(timeoutID );
}, []);
Comme deps = []
, useEffect
Le rappel de l'utilisateur ne sera appelé qu'une seule fois. Ensuite, le callback que vous renvoyez sera appelé lorsque le composant sera démonté.
Quoi qu'il en soit, je vous encourage à créer votre propre useTimeout
de sorte que vous puissiez DRY et simplifier votre code en utilisant setTimeout
de manière déclarative comme le suggère Dan Abramov pour setInterval
sur Rendre setInterval déclaratif avec React Hooks qui est assez similaire :
function useTimeout(callback, delay) {
const timeoutRef = React.useRef();
const callbackRef = React.useRef(callback);
// Remember the latest callback:
//
// Without this, if you change the callback, when setTimeout kicks in, it
// will still call your old callback.
//
// If you add `callback` to useEffect's deps, it will work fine but the
// timeout will be reset.
React.useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Set up the timeout:
React.useEffect(() => {
if (typeof delay === 'number') {
timeoutRef.current = window.setTimeout(() => callbackRef.current(), delay);
// Clear timeout if the components is unmounted or the delay changes:
return () => window.clearTimeout(timeoutRef.current);
}
}, [delay]);
// In case you want to manually clear the timeout from the consuming component...:
return timeoutRef;
}
const App = () => {
const [isLoading, setLoading] = React.useState(true);
const [showLoader, setShowLoader] = React.useState(false);
// Simulate loading some data:
const fakeNetworkRequest = React.useCallback(() => {
setLoading(true);
setShowLoader(false);
// 50% of the time it will display the loder, and 50% of the time it won't:
window.setTimeout(() => setLoading(false), Math.random() * 4000);
}, []);
// Initial data load:
React.useEffect(fakeNetworkRequest, []);
// After 2 second, we want to show a loader:
useTimeout(() => setShowLoader(true), isLoading ? 2000 : null);
return (<React.Fragment>
<button onClick={ fakeNetworkRequest } disabled={ isLoading }>
{ isLoading ? 'LOADING... ' : 'LOAD MORE ' }
</button>
{ isLoading && showLoader ? <div className="loader"><span className="loaderIcon"></span></div> : null }
{ isLoading ? null : <p>Loaded! </p> }
</React.Fragment>);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body,
button {
font-family: monospace;
}
body, p {
margin: 0;
}
#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;
}
.loader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 128px;
background: white;
}
.loaderIcon {
animation: spin linear infinite .25s;
}
@keyframes spin {
from { transform:rotate(0deg) }
to { transform:rotate(360deg) }
}
<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>
En plus de produire un code plus simple et plus propre, cela vous permet d'effacer automatiquement le délai d'attente en passant la commande delay = null
et renvoie également l'ID du délai d'attente, au cas où vous voudriez l'annuler vous-même manuellement (ce n'est pas couvert dans les posts de Dan).
Si vous cherchez une réponse similaire pour setInterval
plutôt que setTimeout
regarde ça : https://stackoverflow.com/a/59274004/3723993 .
Vous pouvez également trouver une version déclarative de setTimeout
et setInterval
, useTimeout
et useInterval
ainsi qu'un useThrottledCallback
écrite en TypeScript dans https://gist.github.com/Danziger/336e75b6675223ad805a88c2dfdcfd4a .