Debounce not working if state is also modified in function

Viewed 206

I've been implementing a search as you type feature and would like to have the function search which handles my API calls to be debounced. Which works fine if I only call the debounced_search function within the event handler, but I also need to update the input value, which should happen immediately.

My issue is now that as soon as I add setInput(newInputValue); to the event handler, the search function is no longer debounced but gets called immediately on every change though I'm using the debounced version.

Why does that happen? And how do I solve this?

Thank you.

import React from 'react'
import Autocomplete from '@mui/material/Autocomplete';
import { selectClasses, TextField } from '@mui/material';
import debounce from 'lodash/debounce';

function Searchbar() {
    const [options, setOptions] = React.useState([]);
    const [input, setInput] = React.useState("");

    const search = (event, newInputValue) => {
        console.log("Debounced Called")
    }

    const debounced_search = debounce(search, 500)

    return (<Autocomplete
        id="free-solo"
        freeSolo
        renderInput={(params) => <TextField {...params} label="Test" />}
        inputValue={input}
        options={options}
        onInputChange={function (event, newInputValue) {
            debounced_search(event, newInputValue);
            setInput(newInputValue);
            console.log("Input Changed")
        }}
    />)
}

export default Searchbar
2 Answers

The reason this isn't working for you is that the function component is called again and again every time there is a state change, which means that everything inside it is re-run each time as well, including your call to debounce(). To deal with this issue, React provides a useCallback() hook:

const debounced_search = useCallback(debounce((event, newInputValue) => {
        console.log("Debounced Called")
}, 500), [])

Note that I've inlined the search function here because there's no need to re-create it on every render either.

This is working for me:

index.js


    import React, { StrictMode } from "react";
    import ReactDOM from "react-dom";

    import App from "./App";

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

App.js


    import React from "react";
    import debounce from "@icaruk/debounce";

    export default function App() {
        const [input, setInput] = React.useState("");

        const search = (input) => {
            console.log(`Debounced Called with ${input}`);
        };

        return (
            <input
                id="free-solo"
                value={input}
                onChange={function (event) {
                    const value = event.target.value;
                    debounce(1000, () => search(value));
                    setInput(value);
                    console.log(`Input Changed to ${value}`);
                }}
            />
        );
    }

Here is the sandbox:
https://codesandbox.io/s/blissful-water-gojli?file=/src/App.js

If you have any questions about the code feel free to ask, I didn't put any comments but maybe something needs explanation.

Related