swr
swr copied to clipboard
useSWRInfinite getKey stale closure?
Bug report
Description / Observed Behavior
I was trying to make multiple dynamic calls using useSWRInfinite as suggested here: my idea was to keep track of the dynamic calls inside an array stored in a useState
, and then in the getKey
simply access the array by index and use the info to make the correct fetch. To trigger the calls, I manually call setSize
when the array changes.
Here's some simple code
const [calls, setCalls] = useState([initalValue]);
const { data, size, setSize } = useSWRInfinite(
(index) => `/${calls[index]}`,
fetcher
);
const modifyArray = () => {
setCalls(/* some new array */);
setSize(/* the new size of the array */);
};
It turns out that getKey
accesses the previous state of calls
: if we have 2 calls and add another one, getKey
will be correctly called 3 times but calls[2]
will be undefined
.
Expected Behavior
I expect the calls
in the getKey
function to be fresh, and correctly hold the current state.
Repro Steps / Code Example
Here's the CodeSandbox. You can see in the console that the value of calls[index]
is undefined
.
Additional Context
SWR version: 1.2.2
I know I should handle multiple calls inside multiple components, but in my case I need to make multiple calls depending on arbitrary user interaction and feed the data directly into only one component (a chart).
@nevnein
Yeah, as you mentioned, that's a stale closure problem.
The setSize
call in modifyArray
references stale getKey
, so it cannot get the result of setCalls
.
This seems to be hard to fix this in useSWRInfinite
. A temporary fix is to wrap setCalls
with flushSync
added in React v18. This is not an ideal fix because it leads to additional rendering.
flushSync(() => setCalls((prev) => [...prev, list[prev.length]]));
setSize((size) => size + 1);