From fdad54db4c16633ef3b0a896047037d3585e8aab Mon Sep 17 00:00:00 2001 From: Sophie Alpert Date: Mon, 9 Oct 2023 11:06:26 -0700 Subject: [PATCH] Explain limitations of useSyncExternalStore with concurrency (#6339) * Explain limitations of useSyncExternalStore with concurrency Doesn't seem like we have this noted anywhere. * Pull out example to code block --- .../reference/react/useSyncExternalStore.md | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/content/reference/react/useSyncExternalStore.md b/src/content/reference/react/useSyncExternalStore.md index 4169e231e..955b751f8 100644 --- a/src/content/reference/react/useSyncExternalStore.md +++ b/src/content/reference/react/useSyncExternalStore.md @@ -57,6 +57,26 @@ The current snapshot of the store which you can use in your rendering logic. * If a different `subscribe` function is passed during a re-render, React will re-subscribe to the store using the newly passed `subscribe` function. You can prevent this by declaring `subscribe` outside the component. +* If the store is mutated during a [non-blocking transition update](/reference/react/useTransition), React will fall back to performing that update as blocking. Specifically, React will call `getSnapshot` a second time just before applying changes to the DOM. If it returns a different value than when it was called originally, React will restart the transition update from scratch, this time applying it as a blocking update, to ensure that every component on screen is reflecting the same version of the store. + +* It's not recommended to _suspend_ a render based on a store value returned by `useSyncExternalStore`. The reason is that mutations to the external store cannot be [marked as non-blocking transition updates](/reference/react/useTransition), so they will trigger the nearest [`Suspense` fallback](/reference/react/Suspense), replacing already-rendered content on screen with a loading spinner, which typically makes a poor UX. + + For example, the following are discouraged: + + ```js + const LazyProductDetailPage = lazy(() => import('./ProductDetailPage.js')); + + function ShoppingApp() { + const selectedProductId = useSyncExternalStore(...); + + // ❌ Calling `use` with a Promise dependent on `selectedProductId` + const data = use(fetchItem(selectedProductId)) + + // ❌ Conditionally rendering a lazy component based on `selectedProductId` + return selectedProductId != null ? : ; + } + ``` + --- ## Usage {/*usage*/} @@ -425,4 +445,4 @@ function ChatIndicator({ userId }) { // ... } -``` \ No newline at end of file +```