Add built-in Suspense cache with support for invalidation (refreshing)#20456
Merged
acdlite merged 30 commits intofacebook:masterfrom Dec 18, 2020
Merged
Add built-in Suspense cache with support for invalidation (refreshing)#20456acdlite merged 30 commits intofacebook:masterfrom
acdlite merged 30 commits intofacebook:masterfrom
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Includes all the basic functionality. I think the only features we've discussed that are missing are 1)
passing initial data to the(Update: done), and 2) providing an AbortSignal to cancel remaining requests on completion.refreshmethodI can open multiple PRs if we want to land the features incrementally. I split it into atomic commits for this purpose, and so it's easier to review.
Most of the commits are already pretty focused, except the last one. I'll work on that.
Technical details
A naive version of this can be built in userspace using the state and context hooks. The internal implementation is nearly identical, but it has two special features that have no userspace equivalent:
Retaining in-progress caches across pings
All the data that loaded as a result of a single transition/update should share the same cache. This includes nested content that gets progressively "filled in" after the initial shell is displayed.
If the shell itself were wrapped in a Cache boundary, such that the cache can commit with suspending, then this is easy: once the boundary mounts, the cache is attached the React tree.
The tricky part is when the shell does not include a cache boundary. In the naive approach, since the cache is not part of the initial tree, it does not get retained; during the retry, a fresh cache is created, leading to duplicate requests and possibly an infinite loop as requests are endlessly created then discarded.
This is the essential problem we faced several years ago when building Simple Cache Provider (later the react-cache package).
Our solution is to retain in-flight caches on the root, associated by lane. The cache cleared from the root once all of the lanes that depend on it finish rendering. [Edit: For now, we're only tracking a single "pooled" cache. We'll add the per-lane map after some planned refactors to the lanes model. Though we still track the lanes that depend on the pooled cache, so that we know when it's OK to clear it.]
Propagating the cache context across spawned tasks
The other tricky bit is that once the initial shell commits, we spawn new tasks to fill in the remaining content. These tasks must use the same cache as the original transition. To implement this, when a Suspense fallback commits, we save a reference to the cache that was used during the attempt to render the primary children.