Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calling setSize before pages finish loading causes useSWRInfinite to trigger multiple requests #3001

Open
FelixZY opened this issue Aug 8, 2024 · 2 comments

Comments

@FelixZY
Copy link

FelixZY commented Aug 8, 2024

Bug report

Description / Observed Behavior

In an infinite scroll scenario, useSWRInfinite would trigger tens of duplicated requests in the span of a few milliseconds.

Expected Behavior

SWR should have triggered only one request

Repro Steps / Code Example

Minimal reproduction set up here: https://github.com/FelixZY/swr-setsize-bug-repro

Using the linked reproduction:

  1. start the development server using npm start
  2. open the console drawer of your browser's development tools (I'm using google chrome)
  3. hover your cursor over the website
  4. scroll the mouse wheel

Expected:

The console output should show

/0: 1
/1: 1
/2: 1
/3: 1
/4: 1
/5: 1
/6: 1
/7: 1
/8: 1
/9: 1

Actual:

Actual output (example)
/0: 1
/1: 1
/1: 2
/1: 3
/1: 4
/1: 5
/1: 6
/1: 7
/1: 8
/1: 9
/1: 10
/1: 11
/2: 1
/2: 2
/2: 3
/2: 4
/2: 5
/2: 6
/2: 7
/2: 8
/2: 9
/2: 10
/2: 11
/2: 12
/2: 13
/2: 14
/2: 15
/2: 16
/3: 1
/3: 2
/3: 3
/3: 4
/3: 5
/3: 6
/3: 7
/3: 8
/3: 9
/3: 10
/3: 11
/3: 12
/3: 13
/3: 14
/3: 15
/3: 16
/3: 17
/3: 18
/3: 19
/4: 1
/4: 2
/4: 3
/4: 4
/4: 5
/4: 6
/4: 7
/4: 8
/4: 9
/4: 10
/4: 11
/4: 12
/4: 13
/4: 14
/4: 15
/4: 16
/4: 17
/4: 18
/4: 19
/4: 20
/5: 1
/5: 2
/5: 3
/5: 4
/5: 5
/5: 6
/5: 7
/5: 8
/5: 9
/5: 10
/5: 11
/5: 12
/5: 13
/5: 14
/5: 15
/5: 16
/5: 17
/5: 18
/5: 19
/5: 20
/6: 1
/6: 2
/6: 3
/6: 4
/6: 5
/6: 6
/6: 7
/6: 8
/6: 9
/6: 10
/6: 11
/6: 12
/6: 13
/6: 14
/6: 15
/6: 16
/6: 17
/6: 18
/6: 19
/6: 20
/7: 1
/7: 2
/7: 3
/7: 4
/7: 5
/7: 6
/7: 7
/7: 8
/7: 9
/7: 10
/7: 11
/7: 12
/7: 13
/7: 14
/7: 15
/7: 16
/7: 17
/7: 18
/7: 19
/7: 20
/8: 1
/8: 2
/8: 3
/8: 4
/8: 5
/8: 6
/8: 7
/8: 8
/8: 9
/8: 10
/8: 11
/8: 12
/8: 13
/8: 14
/8: 15
/8: 16
/8: 17
/8: 18
/8: 19
/8: 20
/9: 1
/9: 2
/9: 3
/9: 4
/9: 5
/9: 6
/9: 7
/9: 8
/9: 9
/9: 10
/9: 11
/9: 12
/9: 13
/9: 14
/9: 15
/9: 16
/9: 17
/9: 18
/9: 19
/9: 20
Screencast.from.2024-08-08.13.31.48.webm

Additional Context

SWR version: 2.2.5

It seems the crucial step is for setSize to be called multiple times before loading has actually finished. You can confirm this by removing delay here:

https://github.com/FelixZY/swr-setsize-bug-repro/blob/22ae6eb8181b227739fd0512444501a3a14bb3d6/src/App.js#L20

@FelixZY
Copy link
Author

FelixZY commented Aug 9, 2024

For now, it is possible to work around the issue using this wrapper function:

import { useCallback } from "react";
import useSWRInfinite, { SWRInfiniteHook } from "swr/dist/infinite";

export const useSWRInfinitePatched: SWRInfiniteHook =
  function useSWRInfinitePatched(this: unknown, ...params: unknown[]) {
    const hook = useSWRInfinite.apply(this, params as any);

    const originalSetSize = hook.setSize;
    hook.setSize = useCallback(
      (sizeOrFn) => {
        const newSize =
          typeof sizeOrFn === "function" ? sizeOrFn(hook.size) : sizeOrFn;

        // Patch for
        // https://github.com/vercel/swr/issues/3001
        if (newSize === hook.size) {
          // Based on
          // https://github.com/vercel/swr/blob/1585a3e37d90ad0df8097b099db38f1afb43c95d/src/infinite/index.ts#L295
          return Promise.resolve() as Promise<undefined>;
        } else {
          return originalSetSize(newSize);
        }
      },
      [originalSetSize, hook.size]
    );

    return hook;
  } as unknown as SWRInfiniteHook;

@jlow2397
Copy link

jlow2397 commented Aug 9, 2024

hello! i'm working on an email app with infinite loading and was struggling to figure out why our api was getting pinged multiple times on fast scroll scenarios. in our case, we have optimistic ui that was loading and increasing the size of the infinite loader, but the api wasn't finished with a subsequent api call.

the crux of the issue is that the useSWRInfinite is playing "catch up" with our optimistic ui. for example, the optimisic ui would set the size to 10, but the api would be still loading the 2nd page of data.

I believe that this probably goes against the use case of useSWRInfinite, where one page should theoretically be loaded before another page can even start loading, and I think this is apparent in the source code:

https://github.com/vercel/swr/blob/main/src/infinite/index.ts#L184

in the "catch up scenario", the 10th call of the fetcher function notices that the pages 2-10 need loading, so it will trigger an api request for pages 2-10, but the 9th call of the fetcher function notices that pages 2-9 need loading so it will trigger an additional api request for pages 2-9, etc.


I figured that this probably won't be fixed with SWR (since it goes against default behavior), and I also have a workaround solution that uses a local size & setSize function + a useEffect to follow the incremental page increases without triggering useSWRInfinite's default functionality for loading the entirety of the undefined dataset

hopefully this helps too!

  const { size, setSize, data } = useSWRInfinite(...)
  const [localSize, setLocalSize] = useState(1)
  // my pagination function uses setLocalSize instead of setSize

  useEffect(() => {
    if (localSize > size) {
      const isFullyLoaded = data?.length === size
      if (isFullyLoaded) {
        setSize(size + 1)
      }
    }
  }, [localSize, size, data?.length])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants