How to use throttle or debounce with React Hook?

后端 未结 17 991
甜味超标
甜味超标 2020-11-30 04:27

I\'m trying to use the throttle method from lodash in a functional component, e.g.:

const App = () => {
  const [value, setValue         


        
相关标签:
17条回答
  • 2020-11-30 05:12

    I've created my own custom hook called useDebouncedEffect that will wait to perform a useEffect until the state hasn't updated for the duration of the delay.

    In this example, your effect will log to the console after you have stopped clicking the button for 1 second.

    App.jsx
    import { useState } from "react";
    import { useDebouncedEffect } from "./useDebouncedEffect";
    
    const App = () => {
      const [value, setValue] = useState(0)
    
      useDebouncedEffect(() => console.log(value), 1000, [value])
    
      return (
        <button onClick={() => setValue(value + 1)}>{value}</button>
      )
    }
    
    useDebouncedEffect.js
    import { useCallback, useEffect } from "react";
    
    export const useDebouncedEffect = (effect, delay , deps) => {
        const callback = useCallback(effect, deps);
    
        useEffect(() => {
            const handler = setTimeout(() => {
                callback();
            }, delay);
    
            return () => {
                clearTimeout(handler);
            };
        }, [callback, delay]);
    }
    
    0 讨论(0)
  • 2020-11-30 05:16

    This is my useDebounce:

    export function useDebounce(callback, timeout, deps) {
        const timeoutId = useRef();
    
        useEffect(() => {
            clearTimeout(timeoutId.current);
            timeoutId.current = setTimeout(callback, timeout);
    
            return () => clearTimeout(timeoutId.current);
        }, deps);
    }
    

    And you can use it like this:

    const TIMEOUT = 500; // wait 500 milliseconds;
    
    export function AppContainer(props) {
        const { dataId } = props;
        const [data, setData] = useState(null);
        //
        useDebounce(
            async () => {
                data = await loadDataFromAPI(dataId);
                setData(data);
            }, 
            TIMEOUT, 
            [dataId]
        );
        //
    }
    
    0 讨论(0)
  • 2020-11-30 05:16

    In my case I also needed to pass the event. Went with this:

    const MyComponent = () => {
      const handleScroll = useMemo(() => {
        const throttled = throttle(e => console.log(e.target.scrollLeft), 300);
        return e => {
          e.persist();
          return throttled(e);
        };
      }, []);
      return <div onScroll={handleScroll}>Content</div>;
    };
    
    0 讨论(0)
  • 2020-11-30 05:16

    I write a simple useDebounce hook which takes cleanup into consideration, just as useEffect works.

    import { useState, useEffect, useRef, useCallback } from "react";
    
    export function useDebounceState<T>(initValue: T, delay: number) {
      const [value, setValue] = useState<T>(initValue);
      const timerRef = useRef(null);
      // reset timer when delay changes
      useEffect(
        function () {
          if (timerRef.current) {
            clearTimeout(timerRef.current);
            timerRef.current = null;
          }
        },
        [delay]
      );
      const debounceSetValue = useCallback(
        function (val) {
          if (timerRef.current) {
            clearTimeout(timerRef.current);
            timerRef.current = null;
          }
          timerRef.current = setTimeout(function () {
            setValue(val);
          }, delay);
        },
        [delay]
      );
      return [value, debounceSetValue];
    }
    
    interface DebounceOptions {
      imediate?: boolean;
      initArgs?: any[];
    }
    
    const INIT_VALUE = -1;
    export function useDebounce(fn, delay: number, options: DebounceOptions = {}) {
      const [num, setNum] = useDebounceState(INIT_VALUE, delay);
      // save actual arguments when fn called
      const callArgRef = useRef(options.initArgs || []);
      // save real callback function
      const fnRef = useRef(fn);
      // wrapped function
      const trigger = useCallback(function () {
        callArgRef.current = [].slice.call(arguments);
        setNum((prev) => {
          return prev + 1;
        });
      }, []);
      // update real callback
      useEffect(function () {
        fnRef.current = fn;
      });
      useEffect(
        function () {
          if (num === INIT_VALUE && !options.imediate) {
            // prevent init call
            return;
          }
          return fnRef.current.apply(null, callArgRef.current);
        },
        [num, options.imediate]
      );
      return trigger;
    }
    

    gist is here: https://gist.github.com/sophister/9cc74bb7f0509bdd6e763edbbd21ba64

    and this is live demo: https://codesandbox.io/s/react-hook-debounce-demo-mgr89?file=/src/App.js

    useage:

    const debounceChange = useDebounce(function (e) {
        console.log("debounced text change: " + e.target.value);
      }, 500);
      // can't use debounceChange directly, since react using event pooling
      function deboucnedCallback(e) {
        e.persist();
        debounceChange(e);
      }
    
    // later the jsx
    <input onChange={deboucnedCallback} />
    
    0 讨论(0)
  • 2020-11-30 05:17

    Here is an actual throttle hook. You can use in a screen or component for all of the functions you want to throttle, and they will share the same throttle. Or you can call useThrottle() multiple times and have different throttles for individual functions.

    Use like this:

    import useThrottle from '../hooks/useThrottle';
    
    const [navigateToSignIn, navigateToCreateAccount] = useThrottle([
            () => { navigation.navigate(NavigationRouteNames.SignIn) },
            () => { navigation.navigate(NavigationRouteNames.CreateAccount) }
        ])
    

    And the hook itself:

    import { useCallback, useState } from "react";
    
    // Throttles all callbacks on a component within the same throttle.  
    // All callbacks passed in will share the same throttle.
    
    const THROTTLE_DURATION = 500;
    
    export default (callbacks: Array<() => any>) => {
        const [isWaiting, setIsWaiting] = useState(false);
    
        const throttledCallbacks = callbacks.map((callback) => {
            return useCallback(() => {
                if (!isWaiting) {
                    callback()
                    setIsWaiting(true)
                    setTimeout(() => {
                        setIsWaiting(false)
                    }, THROTTLE_DURATION);
                }
            }, [isWaiting]);
        })
    
        return throttledCallbacks;
    }
    
    0 讨论(0)
提交回复
热议问题