Skip to content

SWR re-renders the whole paginated data array when optimisticData and data functions return an array #4180

@tandetat

Description

@tandetat

Bug report

Description / Observed Behavior

When using mutate() with both optimisticData and an async data function that return structurally identical data (but different references), SWR triggers two renders even though the data content is the same. This causes mutations to paginated data to re-render the whole array twice.

Observed in useSWRInfinite:

const { data, mutate } = useSWRInfinite(getKey, fetcher);

// This causes TWO renders even if server returns identical data
 mutate(
   async (current) => {
     const result = await api.addItem(item);
     return [...result]; // New reference #1
   },
   {
     optimisticData: (current) => [...current, item], // New reference #2
     revalidate: false
   }
);

Result:

  • First render with optimisticData array
  • Second render with final data array (even though contents are identical)

Expected Behavior

SWR should use compare() function in mutation paths. When the final data from a mutation is structurally identical to the current cached data (including optimistic data), SWR should return the existing reference to avoid a cache update and match the revalidation.

Repro Steps / Code Example

import useSWRInfinite from 'swr/infinite';

  function MyComponent() {
    const { data, mutate } = useSWRInfinite(
      (index) => `/api/items?page=${index}`,
      fetcher
    );

    const addItem = async (item: Item) => {
      // BUG: This causes two renders even if API returns the same item
      await mutate(
        async (currentPages) => {
          const newItem = await fetch('/api/items', {
            method: 'POST',
            body: JSON.stringify(item)
          }).then(r => r.json());

          // Optimistic item and server item have same content
          // but different references
          return currentPages.map((page, i) =>
            i === 0
              ? [newItem, ...page]  // New reference
              : page
          );
        },
        {
          optimisticData: (currentPages) =>
            currentPages.map((page, i) =>
              i === 0
                ? [item, ...page]  // New reference
                : page
            ),
          revalidate: false
        }
      );
    };

    // Renders twice even though optimistic and final data are identical
    return <div>{data?.[0]?.length} items</div>;
  }

Additional Context

SWR version: 2.3.6

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions