Edited code block to include console logs from accepted answer.
I have a search bar component that I have completely stripped down to its simplest possible form:
import React, { useEffect, useRef, useState } from "react";
import axios from "axios";
const resultTypes = ["categories", "submissions", "users"];
const SearchBar = () => {
const [results, setResults] = useState([]);
const [selectedResultType, setSelectedResultType] = useState(resultTypes[0]);
console.log("render --------");
console.log("selectedResultType: " + selectedResultType);
const selectedResultTypeRef = useRef(null);
console.log(
"selectedResultTypeRef.current: " + selectedResultTypeRef.current
);
const fetchResults = async () => {
console.log("fetching...");
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos/3"
);
setResults(response.data);
console.log("setResults: ", response.data);
};
useEffect(() => {
console.log("effect 1 ---------");
selectedResultTypeRef.current = selectedResultType;
console.log(
"selectedResultTypeRef.current: " + selectedResultTypeRef.current
);
}, [selectedResultType]);
useEffect(() => {
console.log("effect 2 ---------");
// No need to debounce fetch when changing result type
fetchResults();
}, [selectedResultTypeRef.current]);
return (
<div className="SearchBar__container">
<div className="SearchBarResults__types">
{resultTypes.map((resultType) => (
<button
key={resultType}
onClick={() => {
console.log("click " + resultType + " -------");
setSelectedResultType(resultType);
}}
>
{resultType}
</button>
))}
</div>
</div>
);
};
export default SearchBar;
When I click between my 3 buttons (categories, submissions, and users), I expect the selectedResultType
to change, which will then change selectedResultTypeRef.current
so I can pass it into a memoized, debounced function. I removed all of this because it's not relevant, but that's the reason I need to use refs instead of just component state.
Here's my problem: When I call the async function and wait for a response to set results, it seems that selectedResultTypeRef.current
only changes (or is recognized as a change) every other click. I've tried different combinations of clicking through the 3 buttons, and it's always every other click that triggers the hook that calls fetchResults
.
Here's the console output using the above code:
Oddly, it fetches twice after missing a fetch for the previous ref change. I really don't know why this is. What's also curious is that when I replace the setResults
line with just a simple console.log(response.data)
, it never misses a hook. Here's the console output from that:
My suspicion is that the setResults
update somehow triggers a re-render of the dom (even though I'm not using the results
state anywhere in this simplified version), and it interferes with selectedResultTypeRef
being recognized as an update that gets captured by the useEffect
hook. I don't know why it's every other update, but I've at least verified its consistent pattern.
Other than that, I'm completely at a loss. I don't know what else to try.