score:113
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.
score:-1
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>;
};
score:-1
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])
}
score:0
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,
};
}
score:0
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} />
score:0
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;
}
score:0
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;
score:0
react-table
has a nice useAsyncDebounce
function featured at https://react-table.tanstack.com/docs/faq#how-can-i-debounce-rapid-table-state-changes
score:0
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
score:1
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 />
}
score:1
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];
};
score:1
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();
};
score:1
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>
)
...
score:1
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;
score:3
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} />
score:3
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]
);
//
}
score:3
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
score:4
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.current)
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>
score:4
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.
score:4
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>
)
}
score:7
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}
);
}
score:14
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>;
}
score:18
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.
score:36
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>
score:62
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"
}]
}
},
Source: stackoverflow.com
Related Query
- How to use throttle or debounce with React Hook?
- How to use callback with useState hook in react
- How to use React useRef hook with typescript?
- How to use React Context with useState hook to share state from different components?
- How to use React Hook Form with Typescript (Input Component)
- How to use useState hook in React with typescript correctly?
- How to use useCallback hook with onChange function REACT
- How to use onChange with react hook on react select to change data base on user select
- How to use a custom React hook to make a POST or DELETE request with Axios
- How to use the react spring hook with react. Basic code not working
- Debounce or throttle with react hook
- how to use react fetch() with useEffect hook and map the fetched data
- How to use array.reduce() with react hook useState, useEffect?
- How to use debounce with React
- How to use react-testing-library and jest with mocked custom react hook updating?
- How to use react hook useeffect with adding event-handler depended on state?
- How to use setInterval with react useEffect hook correctly?
- How to use refs in React with Typescript
- How to use children with React Stateless Functional Component in TypeScript?
- How to use React Router with Electron?
- How to use Redux's Provider with React
- How to use jQuery UI with React JS
- How to use shouldComponentUpdate with React Hooks?
- How to use create-react-app with an older React version?
- How to use React Router with Laravel?
- How to use context api with react router v4?
- How do I use the Firebase onAuthStateChange with the new React Hooks?
- React hook form: How to can I use onChange on React Hook Form Version 7.0
- How to prevent race conditions with react useState hook
- How to use React with Typescript and SASS
More Query from same tag
- How to Add a google font to Next.Js Project locally while using tailwind css
- Can typeof return a falsey value?
- Alter data in object of arrays
- GooglePlacesAutocomplete - passing info to parent component and defining language
- Using React Router to determine ReactApp URL Search
- Function undefined when assigned to variable
- why click event is not working in react js?
- React/Next.js site doesn't load properly in Safari (blank page)
- why my env file with a personal access token is not working in my react app?
- react child/parent component - onChange event just takes one character and not retaining value
- React + TS: How to pass a parameter from redirect function to onClick
- Is it ok to use a wrapper component to pass props in React?
- update the value of nested objects using useState
- JavaScript filter does not update the React local state
- How to load multiple documents in WebView from URL (PDFTron)
- Deck.GL ScatterplotLayer does not render points
- I can't create new app with create-react-app
- Can I stop Material-UI SpeedDial from closing when a SpeedDialAction button is clicked
- How to store images in Objects?
- filter by multiple array by matching object properties
- How to redirect after Nodemailer POST request
- Hello World example with flask and react
- Props to set styled components background image using in React 17
- How can I import a file using React.js, ES6 and Webpack based on the value of a prop?
- Test exception - unstable_flushDiscreteUpdates
- onMouseEnter add a opacity transition
- React Bundle giving error: "Type Error: cannot read property 'ReactCurrentOwner' of undefined"
- axios is not implementing post request
- Decrementing a counter in setState
- How to use map on an array of objects