React hooks - right way to clear timeouts and intervals

后端 未结 6 794
感情败类
感情败类 2020-12-01 02:54

I don\'t understand why is when I use setTimeout function my react component start to infinite console.log. Everything is working, but PC start to lag as hell.

6条回答
  •  挽巷
    挽巷 (楼主)
    2020-12-01 03:21

    The problem is you are calling setTimeout outside useEffect, so you are setting a new timeout every time the component is rendered, which will eventually be invoked again and change the state, forcing the component to re-render again, which will set a new time, which...

    So, as you have already found out, the way to use setTimeout or setInterval with hooks is to wrap them in useEffect, like so:

    React.useEffect(() => {
        const timeoutID = window.setTimeout(() => {
            ...
        }, 1000);
    
        return () => window.clearTimeout(timeoutID );
    }, []);
    

    As deps = [], useEffect's callback will only be called once. Then, the callback you return will be called when the component is unmounted.

    Anyway, I would encourage you to create your own useTimeout hook so that you can DRY and simplify your code by using setTimeout declaratively, as Dan Abramov suggests for setInterval in Making setInterval Declarative with React Hooks, which is quite similar:

    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 (
        
提交回复
热议问题