I was building a good old read-fetch-suggest lookup bar when I found out that my input lost focus on each keypress.
I learnt that because my input component was defined inside the header component enclosing it, changes to the state variable triggered a re-render of the parent which in turn redefined the input component, which caused that behaviour. useCallback was to be used to avoid this.
Now that I did, the state value remains an empty string, even though it's callback gets called (as I see the keystrokes with console.log)
This gets fixed by passing the state and state setter to the input component as props. But I don't quite understand why. I'm guessing that the state and setter which get enclosed in the useCallback get "disconnected" from the ones yield by subsequent calls.
I would thankfully read an explanation clearing this out. Why does it work one way and not the other? How is enclosing scope trated when using useCallback?
Here's the code.
export const Header = () => {
const [theQuery, setTheQuery] = useState("");
const [results, setResults] = useState<ResultType>();
// Query
const runQuery = async () => {
const r = await fetch(`/something`);
if (r.ok) {
setResults(await r.json());
}
}
const debouncedQuery = debounce(runQuery, 500);
useEffect(() => {
if (theQuery.length > 3) {
debouncedQuery()
}
}, [theQuery]);
const SearchResults = ({ results }: { results: ResultType }) => (
<div id="results">{results.map(r => (
<>
<h4><a href={`/linkto/${r.id}`}>{r.title}</a></h4>
{r.matches.map(text => (
<p>{text}</p>
))}
</>
))}</div>
)
// HERE
// Why does this work when state and setter go as
// props (commented out) but not when they're in scope?
const Lookup = useCallback((/* {theQuery, setTheQuery} : any */) => {
return (
<div id='lookup_area'>
<input id="theQuery" value={theQuery}
placeholder={'Search...'}
onChange={(e) => {
setTheQuery(e.target.value);
console.log(theQuery);
}}
type="text" />
</div>
)
}, [])
return (
<>
<header className={`${results ? 'has_results' : ''}`}>
<Lookup /* theQuery={theQuery} setTheQuery={setTheQuery} */ />
</header>
{results && <SearchResults results={results} />}
</>
)
}