I am trying to debounce an input. I have memoized the debounce handler so that it does not change references on each render. I need the input to be bound to a state value as I need to set it elsewhere in my app. The issue is that the input value is never able to be updated as when inside the debounced changeHandler e.target.value always contains previous value, not a new value entered. How can i debounce the input that is bound to a state value?
xport function App() {
const [query, setQuery] = useState("a value");
const changeHandler = (event) => {
console.log(event.target.value); // wrong value
setQuery(event.target.value);
};
const debouncedChangeHandler = useCallback(debounce(changeHandler, 1000), []);
return (
<div>
<input
value={query}
onChange={debouncedChangeHandler}
type="text"
placeholder="Type a query..."
/>
</div>
);
}
sandbox: https://codesandbox.io/s/react-debounce-5pidiy?file=/src/index.js:130-596
CodePudding user response:
I wouldn't try to debounce the keyboard input itself, I'd just wait to validate until the value hadn't changed for while, and then either directly validate it:
const { useState, useEffect, useCallback } = React;
function App() {
const [query, setQuery] = useState("a value");
const changeHandler = (event) => {
setQuery(event.target.value);
};
const validate = useCallback((query) => {
console.log(`Validating "${query}"...`);
});
useEffect(() => {
const timer = setTimeout(() => {
validate(query)
}, 1000);
return () => {
clearTimeout(timer);
}
}, [query]);
return (
<div>
<input
value={query}
onChange={changeHandler}
type="text"
placeholder="Type a query..."
/>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
...or use a separate state member for it:
const { useState, useEffect, useCallback } = React;
function App() {
const [rawQuery, setRawQuery] = useState("a valid value");
const [query, setQuery] = useState(rawQuery);
const changeHandler = (event) => {
console.log(`raw query: ${event.target.value}`);
setRawQuery(event.target.value);
};
const validate = useCallback((query) => {
console.log(`Validating "${query}"...`);
return query.includes("valid");
});
useEffect(() => {
const timer = setTimeout(() => {
console.log(`validate: ${rawQuery}`);
if (validate(rawQuery)) {
console.log("Valid!");
setQuery(rawQuery);
} else {
console.log("Invalid!");
setRawQuery(query);
}
}, 1000);
return () => {
clearTimeout(timer);
}
}, [rawQuery, query]);
return (
<div>
<input
value={rawQuery}
onChange={changeHandler}
type="text"
placeholder="Type a query..."
/>
<div>Value to validate: {query}</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
That makes it easier to do what you said in a comment you wanted, putting the input back to the previous valid value if an invalid one has provided.
(That updating can be wrapped in a hook for reuse.)