How to use throttle or debounce with React Hook?

ReactjsLodashReact HooksThrottling

Reactjs Problem Overview


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

const App = () => {
  const [value, setValue] = useState(0)
  useEffect(throttle(() => console.log(value), 1000), [value])
  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

Since the method inside useEffect is redeclared at each render, the throttling effect does not work.

Does anyone have a simple solution ?

Reactjs Solutions


Solution 1 - Reactjs

After some time passed I'm sure it's much easier to handle things by your own with setTimeout/clearTimeout(and moving that into separate custom hook) than working with functional helpers. Handling later one creates additional challenges right after we apply that to useCallback that can be recreated because of dependency change but we don't want to reset delay running.

original answer below

you may(and probably need) useRef to store value between renders. Just like it's suggested for timers

Something like that

const App = () => {
  const [value, setValue] = useState(0)
  const throttled = useRef(throttle((newValue) => console.log(newValue), 1000))
  
  useEffect(() => throttled.current(value), [value])
  
  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

As for useCallback:

It may work too as

const throttled = useCallback(throttle(newValue => console.log(newValue), 1000), []);

But if we try to recreate callback once value is changed:

const throttled = useCallback(throttle(() => console.log(value), 1000), [value]);

we may find it does not delay execution: once value is changed callback is immediately re-created and executed.

So I see useCallback in case of delayed run does not provide significant advantage. It's up to you.

[UPD] initially it was

  const throttled = useRef(throttle(() => console.log(value), 1000))
  
  useEffect(throttled.current, [value])

but that way throttled.current has bound to initial value(of 0) by closure. So it was never changed even on next renders.

So be careful while pushing functions into useRef because of closure feature.

Solution 2 - Reactjs

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.

Sandbox Example https://codesandbox.io/s/react-use-debounced-effect-6jppw

App.jsx
import { useState } from "react";
import { useDebouncedEffect } from "./useDebouncedEffect";

const App = () => {
  const [value, setValue] = useState(0)

  useDebouncedEffect(() => console.log(value), [value], 1000);

  return (
    <button onClick={() => setValue(value + 1)}>{value}</button>
  )
}

export default App;
useDebouncedEffect.js
import { useEffect } from "react";

export const useDebouncedEffect = (effect, deps, delay) => {
    useEffect(() => {
        const handler = setTimeout(() => effect(), delay);

        return () => clearTimeout(handler);
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [...deps || [], delay]);
}

The comment to disable exhaustive-deps is required unless you want to see a warning because lint will always complain about not having effect as a dependency. Adding effect as a dependency will trigger the useEffect on every render. Instead, you can add the check to useDebouncedEffect to make sure it's being passed all of the dependencies. (see below)

Adding exhaustive dependencies check to useDebouncedEffect

If you want to have eslint check useDebouncedEffect for exhaustive dependencies, you can add it to the eslint config in package.json

  "eslintConfig": {
    "extends": [
      "react-app"
    ],
    "rules": {
      "react-hooks/exhaustive-deps": ["warn", {
        "additionalHooks": "useDebouncedEffect"
      }]
    }
  },

https://github.com/facebook/react/tree/master/packages/eslint-plugin-react-hooks#advanced-configuration

Solution 3 - Reactjs

useThrottle , useDebounce

How to use both
const App = () => {
  const [value, setValue] = useState(0);
  // called at most once per second (same API with useDebounce)
  const throttledCb = useThrottle(() => console.log(value), 1000);
  // usage with useEffect: invoke throttledCb on value change
  useEffect(throttledCb, [value]);
  // usage as event handler
  <button onClick={throttledCb}>log value</button>
  // ... other render code
};
useThrottle (Lodash)
import _ from "lodash"

function useThrottle(cb, delay) {
  const options = { leading: true, trailing: false }; // add custom lodash options
  const cbRef = useRef(cb);
  // use mutable ref to make useCallback/throttle not depend on `cb` dep
  useEffect(() => { cbRef.current = cb; });
  return useCallback(
    _.throttle((...args) => cbRef.current(...args), delay, options),
    [delay]
  );
}

const App = () => {
  const [value, setValue] = useState(0);
  const invokeDebounced = useThrottle(
    () => console.log("changed throttled value:", value),
    1000
  );
  useEffect(invokeDebounced, [value]);
  return (
    <div>
      <button onClick={() => setValue(value + 1)}>{value}</button>
      <p>value will be logged at most once per second.</p>
    </div>
  );
};

function useThrottle(cb, delay) {
  const options = { leading: true, trailing: false }; // pass custom lodash options
  const cbRef = useRef(cb);
  useEffect(() => {
    cbRef.current = cb;
  });
  return useCallback(
    _.throttle((...args) => cbRef.current(...args), delay, options),
    [delay]
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>

useDebounce (Lodash)
import _ from "lodash"

function useDebounce(cb, delay) {
  // ...
  const inputsRef = useRef({cb, delay}); // mutable ref like with useThrottle
  useEffect(() => { inputsRef.current = { cb, delay }; }); //also track cur. delay
  return useCallback(
    _.debounce((...args) => {
        // Debounce is an async callback. Cancel it, if in the meanwhile
        // (1) component has been unmounted (see isMounted in snippet)
        // (2) delay has changed
        if (inputsRef.current.delay === delay && isMounted())
          inputsRef.current.cb(...args);
      }, delay, options
    ),
    [delay, _.debounce]
  );
}

const App = () => {
  const [value, setValue] = useState(0);
  const invokeDebounced = useDebounce(
    () => console.log("debounced", value),
    1000
  );
  useEffect(invokeDebounced, [value]);
  return (
    <div>
      <button onClick={() => setValue(value + 1)}>{value}</button>
      <p> Logging is delayed until after 1 sec. has elapsed since the last invocation.</p>
    </div>
  );
};

function useDebounce(cb, delay) {
  const options = {
    leading: false,
    trailing: true
  };
  const inputsRef = useRef(cb);
  const isMounted = useIsMounted();
  useEffect(() => {
    inputsRef.current = { cb, delay };
  });

  return useCallback(
    _.debounce(
      (...args) => {
        // Don't execute callback, if (1) component in the meanwhile 
        // has been unmounted or (2) delay has changed
        if (inputsRef.current.delay === delay && isMounted())
          inputsRef.current.cb(...args);
      },
      delay,
      options
    ),
    [delay, _.debounce]
  );
}

function useIsMounted() {
  const isMountedRef = useRef(true);
  useEffect(() => {
    return () => {
      isMountedRef.current = false;
    };
  }, []);
  return () => isMountedRef.current;
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>


Customizations

1. You might replace Lodash with your own throttle or debounce code, like:

const debounceImpl = (cb, delay) => {
  let isDebounced = null;
  return (...args) => {
    clearTimeout(isDebounced);
    isDebounced = setTimeout(() => cb(...args), delay);
  };
};

const throttleImpl = (cb, delay) => {
  let isThrottled = false;
  return (...args) => {
    if (isThrottled) return;
    isThrottled = true;
    cb(...args);
    setTimeout(() => {
      isThrottled = false;
    }, delay);
  };
};

const App = () => {
  const [value, setValue] = useState(0);
  const invokeThrottled = useThrottle(
    () => console.log("throttled", value),
    1000
  );
  const invokeDebounced = useDebounce(
    () => console.log("debounced", value),
    1000
  );
  useEffect(invokeThrottled, [value]);
  useEffect(invokeDebounced, [value]);
  return <button onClick={() => setValue(value + 1)}>{value}</button>;
};

function useThrottle(cb, delay) {
  const cbRef = useRef(cb);
  useEffect(() => {
    cbRef.current = cb;
  });
  return useCallback(
    throttleImpl((...args) => cbRef.current(...args), delay),
    [delay]
  );
}

function useDebounce(cb, delay) {
  const cbRef = useRef(cb);
  useEffect(() => {
    cbRef.current = cb;
  });
  return useCallback(
    debounceImpl((...args) => cbRef.current(...args), delay),
    [delay]
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>

2. useThrottle can be shortened up, if always used with useEffect (same for useDebounce):

const App = () => {
  // useEffect now is contained inside useThrottle
  useThrottle(() => console.log(value), 1000, [value]);
  // ...
};

const App = () => {
  const [value, setValue] = useState(0);
  useThrottle(() => console.log(value), 1000, [value]);
  return (
    <div>
      <button onClick={() => setValue(value + 1)}>{value}</button>
      <p>value will be logged at most once per second.</p>
    </div>
  );
};

function useThrottle(cb, delay, additionalDeps) {
  const options = { leading: true, trailing: false }; // pass custom lodash options
  const cbRef = useRef(cb);
  const throttledCb = useCallback(
    _.throttle((...args) => cbRef.current(...args), delay, options),
    [delay]
  );
  useEffect(() => {
    cbRef.current = cb;
  });
  // set additionalDeps to execute effect, when other values change (not only on delay change)
  useEffect(throttledCb, [throttledCb, ...additionalDeps]);
}

ReactDOM.render(<App />, document.getElementById("root"));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js" integrity="sha256-VeNaFBVDhoX3H+gJ37DpT/nTuZTdjYro9yBruHjVmoQ=" crossorigin="anonymous"></script>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>
<div id="root"></div>

Solution 4 - Reactjs

It could be a tiny custom hook, like this:

useDebounce.js

import React, { useState, useEffect } from 'react';

export default (value, timeout) => {
    const [state, setState] = useState(value);

    useEffect(() => {
        const handler = setTimeout(() => setState(value), timeout);

        return () => clearTimeout(handler);
    }, [value, timeout]);

    return state;
}

Usage example:

import React, { useEffect } from 'react';

import useDebounce from '/path/to/useDebounce';

const App = (props) => {
    const [state, setState] = useState({title: ''});    
    const debouncedTitle = useDebounce(state.title, 1000);

    useEffect(() => {
        // do whatever you want with state.title/debouncedTitle
    }, [debouncedTitle]);        

    return (
		// ...
    );
}
// ...

Note: As you probably know, useEffect always run on initial render, and because of that if you use my answer, you will probably see your component's render runs twice, don't worry, you just need to writing another custom hook. check out my other answer for more info.

Solution 5 - Reactjs

Debounce with help of useCallback hook.

import React, { useState, useCallback } from 'react';
import debounce from 'lodash.debounce';

function App() {
	const [value, setValue] = useState('');
	const [dbValue, saveToDb] = useState(''); // would be an API call normally

	// highlight-starts
	const debouncedSave = useCallback(
		debounce(nextValue => saveToDb(nextValue), 1000),
		[], // will be created only once initially
	);
	// highlight-ends

	const handleChange = event => {
		const { value: nextValue } = event.target;
		setValue(nextValue);
		// Even though handleChange is created on each render and executed
		// it references the same debouncedSave that was created initially
		debouncedSave(nextValue);
	};

	return <div></div>;
}

Solution 6 - Reactjs

I wrote two simple hooks (use-throttled-effect and use-debounced-effect) for this use case maybe it wil be useful for someone else looking for a simple solution.

import React, { useState } from 'react';
import useThrottledEffect  from 'use-throttled-effect';
 
export default function Input() {
  const [count, setCount] = useState(0);
 
  useEffect(()=>{
    const interval = setInterval(() => setCount(count=>count+1) ,100);
    return ()=>clearInterval(interval);
  },[])
  
  useThrottledEffect(()=>{
    console.log(count);     
  }, 1000 ,[count]);
  
  return (
    {count}
  );
}

Solution 7 - Reactjs

I'd like to join the party with my throttlled and debounced input using useState:

// import { useState, useRef } from 'react' // nomral import
const { useState, useRef } = React // inline import

// Throttle

const ThrottledInput = ({ onChange, delay = 500 }) => {
  const t = useRef()
  
  const handleChange = ({ target }) => {
    if (!t.current) {
      t.current = setTimeout(() => {
        onChange(target.value)
        clearTimeout(t)
        t.current = null
      }, delay)
    }
  }
  
  return (
    <input
      placeholder="throttle"
      onChange={handleChange}
    />
  )
}


// Debounce

const DebouncedInput = ({ onChange, delay = 500 }) => {
  const t = useRef()
  
  const handleChange = ({ target }) => {
    clearTimeout(t.current)
    t.current = setTimeout(() => onChange(target.value), delay)
  }
  
  return (
    <input
      placeholder="debounce"
      onChange={handleChange}
    />
  )
}

// ----

ReactDOM.render(<div>
  <ThrottledInput onChange={console.log} />
  <DebouncedInput onChange={console.log} />
</div>, document.getElementById('root'))

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Solution 8 - Reactjs

And one more implementation. Custom hook:

function useThrottle (func, delay) {
  const [timeout, saveTimeout] = useState(null);

  const throttledFunc = function () {
    if (timeout) {
      clearTimeout(timeout);
    }

    const newTimeout = setTimeout(() => {
      func(...arguments);
      if (newTimeout === timeout) {
        saveTimeout(null);
      }
    }, delay);

    saveTimeout(newTimeout);
  }

  return throttledFunc;
}

and usage:

const throttledFunc = useThrottle(someFunc, 200);

Hope that will help someone.

Solution 9 - Reactjs

You can use useMemo hook to optimize your throttled event handler

Example code below:

const App = () => {
  const [value, setValue] = useState(0);

  // ORIGINAL EVENT HANDLER
  function eventHandler(event) {
    setValue(value + 1);
  }

  // THROTTLED EVENT HANDLER
  const throttledEventHandler = useMemo(() => throttle(eventHandler, 1000), [value]);
  
  return (
    <button onClick={throttledEventHandler}>Throttled Button with value: {value}</button>
  )
}

Solution 10 - Reactjs

Using lodash's debounce function here is what I do:

import debounce from 'lodash/debounce'

// The function that we want to debounce, for example the function that makes the API calls
const getUsers = (event) => {
// ...
}


// The magic!
const debouncedGetUsers = useCallback(debounce(getUsers, 500), [])

In your JSX:

<input value={value} onChange={debouncedGetUsers} />

Solution 11 - Reactjs

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]
    );
    //
}

Solution 12 - Reactjs

I just came up with the following pattern when trying to solve an issue with stale state:

We can store the debounced function in a ref and update it each time the component rerenders in useEffect like this:

  // some state
  const [counter, setCounter] = useState(0);

  // store a ref to the function we will debounce
  const increment = useRef(null);

  // update the ref every time the component rerenders
  useEffect(() => {
    increment.current = () => {
      setCounter(counter + 1);
    };
  });

  // debounce callback, which we can call (i.e. in button.onClick)
  const debouncedIncrement = useCallback(
    debounce(() => {
      if (increment) {
        increment.current();
      }
    }, 1500),
    []
  );

  // cancel active debounces on component unmount
  useEffect(() => {
    return () => {
      debouncedIncrement.cancel();
    };
  }, []);

Code sandbox: https://codesandbox.io/s/debounced-function-ref-pdrfu?file=/src/index.js

I hope this will save someone a few hours of struggling

Solution 13 - Reactjs

I use something like this and it works great:

let debouncer = debounce(
  f => f(),
  1000,
  { leading: true }, // debounce one on leading and one on trailing
);

function App(){
   let [state, setState] = useState();

   useEffect(() => debouncer(()=>{
       // you can use state here for new state value
   }),[state])

   return <div />
}

Solution 14 - Reactjs

I'm pretty late to this, but here's a way to debounce setState()

/**
 * Like React.setState, but debounces the setter.
 * 
 * @param {*} initialValue - The initial value for setState().
 * @param {int} delay - The debounce delay, in milliseconds.
 */
export const useDebouncedState = (initialValue, delay) => {
  const [val, setVal] = React.useState(initialValue);
  const timeout = React.useRef();
  const debouncedSetVal = newVal => {
    timeout.current && clearTimeout(timeout.current);
    timeout.current = setTimeout(() => setVal(newVal), delay);
  };

  React.useEffect(() => () => clearTimeout(timeout.current), []);
  return [val, debouncedSetVal];
};

Solution 15 - Reactjs

const useDebounce = (func: any) => {
	const debounceFunc = useRef(null);

	useEffect(() => {
		if (func) {
			// @ts-ignore
			debounceFunc.current = debounce(func, 1000);
		}
	}, []);

	const debFunc = () => {
		if (debounceFunc.current) {
			return debounceFunc.current;
		}
		return func;
	};
	return debFunc();
};

Solution 16 - Reactjs

I made a simple hook to create throttle instances.

It takes a slightly different approach, passing in the function to call each time rather that trying to wrap it and manage mutations. A lot of the other solutions don't account for the function to call potentially changing. Pattern works well with throttle or debounce.

// useThrottle.js
import React, { useCallback } from 'react';
import throttle from 'lodash/throttle';

export function useThrottle(timeout = 300, opts = {}) {
  return useCallback(throttle((fn, ...args) => {
    fn(...args);
  }, timeout, opts), [timeout]);
}

Sample usage:

...
const throttleX = useThrottle(100);

const updateX = useCallback((event) => {
  // do something!
}, [someMutableValue])

return ( 
 <div onPointerMove={(event) => throttleX(updateX, event)}></div>
)
...

Solution 17 - Reactjs

I believe this hook works properly by giving the option to fire immediately.

import { useState, useRef, useEffect } from 'react';

const useDebounce = <T>(
  value: T,
  timeout: number,
  immediate: boolean = true
): T => {
  const [state, setState] = useState<T>(value);
  const handler = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  useEffect(() => {
    if (handler.current) {
      clearTimeout(handler.current);
      handler.current = undefined;
    } else if (immediate) {
      setState(value);
    }

    handler.current = setTimeout(() => {
      setState(value);
      handler.current = undefined;
    }, timeout);
  }, [value, timeout, immediate]);

  return state;
};

export default useDebounce;

Solution 18 - Reactjs

If you are using it in handler, I am fairly certain this is the way to do it.

function useThrottleScroll() {
  const savedHandler = useRef();

  function handleEvent() {}

  useEffect(() => {
    savedHandleEvent.current = handleEvent;
  }, []);

  const throttleOnScroll = useRef(throttle((event) => savedHandleEvent.current(event), 100)).current;

  function handleEventPersistence(event) {
    return throttleOnScroll(event);
  }

  return {
    onScroll: handleEventPersistence,
  };
}

Solution 19 - Reactjs

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} />

Solution 20 - Reactjs

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;
}

Solution 21 - Reactjs

Here's a simple hook to debounce your calls.

To use the below code, all you have to do is declare it as so

const { debounceRequest } = useDebounce(someFn);

And, then call it as so

debounceRequest(); 

Implementation is shown below

import React from "react";

const useDebounce = (callbackFn: () => any, timeout: number = 500) => {
const [sends, setSends] = React.useState(0);

const stabilizedCallbackFn = React.useCallback(callbackFn, [callbackFn]);

const debounceRequest = () => {
  setSends(sends + 1);
};

// 1st send, 2nd send, 3rd send, 4th send ...
// when the 2nd send comes, then 1st set timeout is cancelled via clearInterval
// when the 3rd send comes, then 2nd set timeout is cancelled via clearInterval
// process continues till timeout has passed, then stabilizedCallbackFn gets called
// return () => clearInterval(id) is critical operation since _this_ is what cancels 
//  the previous send.
// *🎗 return () => clearInterval(id) is called for the previous send when a new send 
// is sent. Essentially, within the timeout all but the last send gets called.

React.useEffect(() => {
  if (sends > 0) {
     const id = window.setTimeout(() => {
       stabilizedCallbackFn();
       setSends(0);
     }, timeout);
     return () => {
      return window.clearInterval(id);
     };
  }
 }, [stabilizedCallbackFn, sends, timeout]);

 return {
   debounceRequest,
 };
};

export default useDebounce;

Solution 22 - Reactjs

react-table has a nice useAsyncDebounce function featured at https://react-table.tanstack.com/docs/faq#how-can-i-debounce-rapid-table-state-changes

Solution 23 - Reactjs

function myThrottle(callback, delay) {
  var previousTime = 0;
  return function (...args) {
    let currentTime = Date.now();
    let gap = currentTime - previousTime;
    if (gap > 0) {
      previousTime = currentTime + delay;
      callback.call(this, ...args);
    }
    return;
  };
}

Use the below code inside your functional component.

const memoizedCallback = useMemo(() => myThrottle(callback, 3000), []);

Use memoizedCallback as a callback

Solution 24 - Reactjs

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>;
};

Solution 25 - Reactjs

My solution is similar to this https://stackoverflow.com/a/68357888/6083689 (features useMemo), however I'm passing the argument directly to debounced function in useEffect, instead of treating it as dependency. It solves the problem of re-creating the hooks by separating the arguments (which supposed to be re-created) and debounced function (which shouldn't be re-created).

const MyComponent: FC<Props> = ({ handler, title }) => {
  const payload = useMemo<Payload>(() => ({ title }), [title])
  const debouncedHandler = useMemo(() => debounce(handler, 1000), [handler])

  useEffect(() => debouncedHandler(payload), [payload, debouncedHandler])
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlexandre AnnicView Question on Stackoverflow
Solution 1 - ReactjsskyboyerView Answer on Stackoverflow
Solution 2 - ReactjsTodd SkeltonView Answer on Stackoverflow
Solution 3 - Reactjsford04View Answer on Stackoverflow
Solution 4 - ReactjsMehdi DehghaniView Answer on Stackoverflow
Solution 5 - ReactjsJaskaran SinghView Answer on Stackoverflow
Solution 6 - ReactjsSaman MohamadiView Answer on Stackoverflow
Solution 7 - ReactjsgazdagergoView Answer on Stackoverflow
Solution 8 - ReactjsAliakseiView Answer on Stackoverflow
Solution 9 - ReactjsRahul GuptaView Answer on Stackoverflow
Solution 10 - ReactjsEngr.MTHView Answer on Stackoverflow
Solution 11 - ReactjsAgus SyahputraView Answer on Stackoverflow
Solution 12 - ReactjsnsimeonovView Answer on Stackoverflow
Solution 13 - Reactjshossein alipourView Answer on Stackoverflow
Solution 14 - ReactjsWhatabrainView Answer on Stackoverflow
Solution 15 - Reactjscomalex3View Answer on Stackoverflow
Solution 16 - ReactjsnverbaView Answer on Stackoverflow
Solution 17 - ReactjsRoland QuastView Answer on Stackoverflow
Solution 18 - Reactjsuser1730335View Answer on Stackoverflow
Solution 19 - ReactjsJessView Answer on Stackoverflow
Solution 20 - ReactjsJeff PadgettView Answer on Stackoverflow
Solution 21 - ReactjsSangeet AgarwalView Answer on Stackoverflow
Solution 22 - ReactjsEdgar ManukyanView Answer on Stackoverflow
Solution 23 - ReactjsGAURAVView Answer on Stackoverflow
Solution 24 - ReactjsNeluView Answer on Stackoverflow
Solution 25 - ReactjsArtur BilskiView Answer on Stackoverflow