How can I get actual prop values in React Functional Component debounced callbacks, It worked in React Class Component, but I have no idea how to reach this behavior in functional component using hooks.
import React from "react";
import ReactDOM from "react-dom";
import debounce from "lodash.debounce";
const TestFunc = ({ count, onClick }) => {
const handleClick = debounce(() => {
onClick();
console.log(count);
}, 500);
return (
<div>
<button type="button" onClick={handleClick}>
Func: {count}
</button>
</div>
);
};
class TestClass extends React.Component {
handleClick = debounce(() => {
this.props.onClick();
console.log(this.props.count);
}, 500);
render() {
return (
<div>
<button type="button" onClick={this.handleClick}>
Class: {this.props.count}
</button>
</div>
);
}
}
const App = () => {
const [countClass, setCountClass] = React.useState(0);
const [countFunc, setCountFunc] = React.useState(0);
return (
<div>
<TestFunc count={countFunc} onClick={() => setCountFunc(countFunc + 1)} />
<TestClass
count={countClass}
onClick={() => setCountClass(countClass + 1)}
/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
When you click on functional component button, it logs the previous count
prop value to console, but it's already changed by calling onClick
handler, in the same time the class component button would log the actual count
prop value after it was incremented by onClick
handler. So, how can I get actual prop values in functional component?
Here's a simple debounce hook (written in TypeScript)
import { useEffect, useRef } from "react";
export function useDebouncedCallback<A extends any[]>(
callback: (...args: A) => void,
wait: number
) {
// track args & timeout handle between calls
const argsRef = useRef<A>();
const timeout = useRef<ReturnType<typeof setTimeout>>();
function cleanup() {
if(timeout.current) {
clearTimeout(timeout.current);
}
}
// make sure our timeout gets cleared if
// our consuming component gets unmounted
useEffect(() => cleanup, []);
return function debouncedCallback(
...args: A
) {
// capture latest args
argsRef.current = args;
// clear debounce timer
cleanup();
// start waiting again
timeout.current = setTimeout(() => {
if(argsRef.current) {
callback(...argsRef.current);
}
}, wait);
};
}
Example for your use case:
const handleClick = useDebouncedCallback(() => {
onClick();
console.log(count);
}, 500);
...
<button type="button" onClick={handleClick}>
Func: {count}
</button>
Also works for cases that pass arguments:
const handleChange = useDebouncedCallback((event) => {
console.log(event.currentTarget.value);
}, 500);
<input onChange={handleChange}/>
You need to make a few changes to use debounced method
with hook
useCallback
hook so that the debounced function is only created once on the initial render.setCountFunc(count => count + 1)
so that the child components re-render with the updated valueWorking demo below
const TestFunc = ({ count, onClick }) => {
const handleClick = React.useCallback((count) =>{
const click = _.debounce((count) => {
onClick();
console.log(count);
}, 500)
click(count);
}, []);
console.log(count, 'render');
return (
<div>
<button type="button" onClick={() => handleClick(count)}>
Func: {count}
</button>
</div>
);
};
class TestClass extends React.Component {
handleClick = _.debounce(() => {
this.props.onClick();
console.log(this.props.count);
}, 500);
render() {
return (
<div>
<button type="button" onClick={this.handleClick}>
Class: {this.props.count}
</button>
</div>
);
}
}
const App = () => {
const [countClass, setCountClass] = React.useState(0);
const [countFunc, setCountFunc] = React.useState(0);
return (
<div>
<TestFunc count={countFunc} onClick={() => setCountFunc(count => count + 1)} />
<TestClass
count={countClass}
onClick={() => setCountClass(countClass + 1)}
/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root" />
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With