swr icon indicating copy to clipboard operation
swr copied to clipboard

useSWRInfinite getKey stale closure?

Open nevnein opened this issue 2 years ago • 1 comments

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 avatar May 10 '22 16:05 nevnein

@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);

koba04 avatar Jul 22 '22 15:07 koba04