Store update that suspends behind an unchanged memo is lost: the boundary retry bails out and the UI never updates (0.2.4)
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 18/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- react, typescript
- Ambito
- frontend
Direzione di ricerca
Start with the suspense path in packages/redact/src/dom/features/suspense/full.ts, around realHandleSuspended, where RenderSuspenseCapture is thrown without the thenable. Then read renderUpdates and the memo bailout in packages/redact/src/dom/features/memo/full.ts to see why a pending useSyncExternalStore subscriber is skipped on retry. Reproduce with the snippet in the issue, using the ?control flag as the baseline. Done means the store update appears after the promise resolves under memo, matching React 19.3.1.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
🤖 Issue drafted with Claude Code. Reproduced and verified before posting.
Version: @tanstack/redact 0.2.4 (also 0.2.3). Baseline: react/react-dom 19.3.1. Chromium, production build with vite 8.3.5, @vitejs/plugin-react 6.2.1 and the redact() plugin.
A component subscribed with useSyncExternalStore sits behind a memo wrapper with unchanged props. When the store changes and the component suspends, Redact never shows the new content: the screen keeps the old UI after the promise has resolved, with no error. React shows the new content once the promise resolves.
Reproduction
Vite app with react() + redact(), then call go() from the console:
import { Suspense, memo, use, useSyncExternalStore } from "react"
import { createRoot } from "react-dom/client"
let user = null
const listeners = new Set()
const subscribe = (l) => (listeners.add(l), () => listeners.delete(l))
const getUser = () => user
let orders
const loadOrders = () => (orders ??= new Promise((resolve) => setTimeout(() => resolve("orders"), 100)))
function Account() {
const current = useSyncExternalStore(subscribe, getUser)
return <p>{current ? `${current}: ${use(loadOrders())}` : "logged out"}</p>
}
const Wrapper = memo(() => <Account />)
const control = new URLSearchParams(location.search).has("control")
createRoot(document.getElementById("root")).render(
<Suspense fallback="loading">{control ? <Account /> : <Wrapper />}</Suspense>,
)
window.go = () => { user = "alice"; listeners.forEach((l) => l()) }
Result 2 s after go(), 10 runs each:
| React 19.3.1 | Redact 0.2.4 | |
|---|---|---|
As written (behind memo) |
10/10 alice: orders |
10/10 logged out |
?control (no memo) |
10/10 alice: orders |
10/10 alice: orders |
The Redact page still shows logged out 11 s after go(), so the update is lost, not delayed.
Real-world impact
On a TanStack Start storefront running 0.2.3, a successful sign-up (a useActionState action, then queryClient.invalidateQueries() and router.invalidate()) left the sign-up form on screen. The session was created, and a reload showed the account page. With React the account page appears right away.
Likely cause
Tracing the repro:
- The store notification schedules
Account, which renders on its own inrenderUpdatesand suspends. realHandleSuspendedthrowsRenderSuspenseCapture(boundary), which carries the boundary but not the thenable.renderUpdatesrewinds and reschedules the boundary.Account's pending flag was cleared byrerenderFiberbefore it suspended.- The boundary re-renders its children. The
memosees equal props and bails out (memo/full.ts#L21-L36), soAccountis not rendered, nothing suspends, and nothing waits for the promise.
The update is dropped. In React it stays pending on the fiber until it commits, and the boundary retries when the promise resolves.
The React 19.3 audit already fixed the context version of this ("equal-prop memo wrappers do not need to render to reach their consumers. Suspended primary content can retry when its context changes"). External-store subscribers seem to need the same treatment.
Restoring fiber.g = true after the rewind was not enough in my test on 0.2.4, because the boundary's retry still stops at the memo. The fix probably needs the retry to reach pending descendants, like the context propagation path does.
Related: #129, where a pending subscriber under the same suspended boundary renders with rewound props.
- Lingua principale
- TypeScript
- Stelle
- 537
- Fork
- 22
- Merge medio
- 2h 19m
- PR unite (30g)
- 24
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di TanStack/redact
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 42/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 18/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 18/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di TanStack/redact
Issue simili
-
🐛 bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
LibreChat-AI/LibreChat#17733 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
TypeStrong/typedoc#3150 ·
-
accessibility bug good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
I maintainer di solito rispondono entro 1 giorno
-
Normalize image-source MIME types before SVG dispatchForse già presa @HarsH-1877 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
tscircuit/image-utils#58 · 1 commento ·
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 74/100
I maintainer di solito rispondono entro 2 giorni