Skip to content

Avoid creating new promises for deduped revalidation - #4282

Draft
devjiwonchoi wants to merge 3 commits into
mainfrom
jiwon/revalidate-promise-caching
Draft

devjiwonchoi wants to merge 3 commits into
mainfrom
jiwon/revalidate-promise-caching

Conversation

@devjiwonchoi

@devjiwonchoi devjiwonchoi commented Jun 24, 2026

Copy link
Copy Markdown
Member

Tip

Recommended to review commit by commit and hide whitespace.

Why?

SWR already dedupes the actual fetch, so repeated renders share one network request.

But revalidate was an async function, which meant every call returned a new promise. With suspense: true, React could see a different promise from revalidate() on each render, even though SWR was waiting for the same deduped request underneath.

SWR worked around this by instrumenting the promise with status and value. This change instead uses a cached revalidation promise so use() does not see a new promise for the same request.

How?

Store the current deduped revalidation promise in FETCH[key] and reuse it while the same request is active. The existing fetch cleanup still clears the whole FETCH[key] entry, including the cached promise.

When previous data is available, start revalidation but skip use(revalidation), so SWR keeps showing the previous data instead of suspending.

React Suspense retries track the thenable object passed to use(). An async revalidate function always creates a new outer promise, even when the underlying fetch request is deduped.

Move the async work into an inner function and return that wrapper promise explicitly. This does not cache the promise yet, but it makes the returned promise available for a follow-up change to store and reuse.
React Suspense retries need to see the same thenable while a request is pending. SWR already dedupes the fetcher result, but revalidate returns a wrapper promise that resolves after the request updates cache state.

Store that wrapper promise in the fetch cache for deduped revalidations and return it on later deduped calls. This lets retries reuse the exact revalidation promise without changing non-deduped revalidation or mutation invalidation semantics.
@devjiwonchoi devjiwonchoi changed the title Cache revalidation promises for Suspense retries Avoid creating new promises for deduped revalidation Jun 24, 2026
Suspense only needs to unwrap the revalidation promise when there is no returned data to show. When keepPreviousData supplies returnedData, starting the deduped revalidation without passing it to use() preserves the old data and removes the need to mark the wrapper promise fulfilled by hand.

Add a regression test so key changes with keepPreviousData do not show the fallback during revalidation.
@devjiwonchoi
devjiwonchoi force-pushed the jiwon/revalidate-promise-caching branch from 1168a32 to 80015f8 Compare June 24, 2026 14:11
Comment thread src/index/use-swr.ts
Comment on lines +834 to +837
if (isUndefined(returnedData)) {
// No current or previous data is available to render, so suspend on the request.
use(revalidation)
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't that still conditionally suspending on data usage? If we have data, we don't suspend. If we don't have data, we do suspend. I don't get the relationship between revalidation and returned data. I feel like we should just always use() the data and SWR should set the revalidation Promise into the data if we don't have any currently.

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

Successfully merging this pull request may close these issues.

2 participants