Here is debounce function:
const debounce = (func, delay=1000) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(()=>func(...args), delay);
}
}
export default debounce
And here is useHomeOnIdle:
import { useCallback, useEffect, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import debounce from '../js/debounce'
const useHomeOnIdle = (time=5000) => {
// If mouse is idle for 5 seconds, redirect to home page
const navigate = useNavigate();
const debouncedNavigate = debounce(navigate, time);
//const debouncedNavigate = useCallback(() => debounce(navigate, time), [navigate, time]);
//const debouncedNavigate = useMemo(()=>debounce(navigate, time), [navigate, time]);
console.log('debouncedNavigate', debouncedNavigate);
// useCallback to keep the same function reference for the event listener
const mouseMoveHandler = useCallback(() => {
debouncedNavigate('/');
},[debouncedNavigate]);
useEffect(() => {
// Call once to start the timer when the component is mounted
mouseMoveHandler();
window.addEventListener('mousemove', mouseMoveHandler);
return () => window.removeEventListener('mousemove', mouseMoveHandler);
}, [mouseMoveHandler])
}
export default useHomeOnIdle
If I leave this as is in some case site goes to main page even if I move mouse constantly. This is due to rerenders and not cleaning up properly. And it looks like memoization debouncedNavigate solve the problem. Here is what I tried:
1.
const debouncedNavigate = useCallback(() => debounce(navigate, time), [navigate, time]);
This is not working at all and debouncedNavigate is:
() => debounce(navigate, time)
2.
const debouncedNavigate = useCallback(debounce(navigate, time), [navigate, time]);
This is working but eslint throws:
React Hook useCallback received a function whose dependencies are unknown. Pass an inline function instead.eslint
debouncedNavigate is:
debouncedNavigate (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(()=>func(...args), delay);
}
3.
const debouncedNavigate = useMemo(()=>debounce(navigate, time), [navigate, time]);
This is working and debouncedNavigate is:
debouncedNavigate (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(()=>func(...args), delay);
}
And there is no eslint warning
4.
const debouncedNavigate = useMemo(debounce(navigate, time), [navigate, time]);
This is not working at all and throws:
Uncaught TypeError: debouncedNavigate is not a function
Question: So it looks like best option is 3. because it's working and no warnings but should I use useMemo to memoize function? And why adding or removing ()=> from useMemo/useCallback make things work or not. And it looks like:
useMemo(()=>debounce(navigate, time), [navigate, time]);
returns same value as:
useCallback(debounce(navigate, time), [navigate, time]);
I'm confused with this useMemo/useCallback usage
When you call
debounce
during render it returns a new function identity. Consequently,debouncedNavigate
is a new function on every render. This also means yourmouseMoveHandler
changes on every render which means your event binding is for a different function each time. You are on the right track. You just want a memoized debounce function.useMemo
takes a function that returns a value and that value is memoized.Editing to add more explanation:
When you pass a function to
useCallback
that function is memoized and returned by that call until a dependency changes. When you have an inline function it's great for the linter because it can make sure your dependencies are all correct, but in reality ever render cycle is defining a new function and passing it in only for it to get thrown out if a dependency hasn't changed. It maintains a stable function identity, though. This is why your example in #2 works, even though it gives you a warning. It is creating a new function on every render when you calldebounce
but they are all getting thrown out except for the first one.When you call
useMemo
it receives a function that returns a value to memoize. That inline function identity changes on every render as well. However, it's only called when a dependency changes. So in the case ofuseMemo
it only ever callsdebounce
one time.