FirebaseExtended / FirebaseExtended/reactfire

Suspense mode resumes with the previous observable's value on React 19

Open
#793 0 comments 0 reactions 0 assignees View on GitHub
v5
Dominant language
TypeScript
Stars
3.6k
Forks
403
Avg merge
14h 53m
Merged PRs (30d)
5

Description

`useObservable` in suspense mode suspends correctly when the observable and `observableId` are swapped, then resumes rendering the **previous** observable's value.

Found by the first test run ever executed against React 19 (#789); nothing had run against 19 before, only type-checks.

## Reproduction

`test/useObservable.test.tsx`, `emits the new observable's value if the observable is swapped out`. The test renders a component under `React.Suspense`, emits `Jeff` on the first observable, swaps both the observable and the `observableId`, rerenders, then emits `James` on the second.

- **React 18.2.0:** the fallback shows, then the component resumes with `James`. Passes.
- **React 19.2.8:** the fallback shows (that assertion passes), then the component resumes with `Jeff`. Fails.

Deterministic: 3 runs each way, same `@testing-library/react` 16.3.2, only the React runtime swapped via `npm install --no-save react@19 react-dom@19`. No emulators involved, so `npx vitest run useObservable` reproduces it.

## What is known and not known

- The suspend half works. Only the resume is wrong, so this is not the known throw-vs-`use()` question: the 08-07 spike found the `use()` swap is a behavioural no-op on 19 and that React 19 emits no warning for the current throw form. Nothing here contradicts that.
- **The cause is not diagnosed.** The plumbing involved (`SuspenseSubject`, the `preloadedObservables` cache keyed by `observableId`, and the `use-sync-external-store` shim delegating to React's own `useSyncExternalStore` on 19) overlaps what #790 covers, but no connection has been established and it should not be assumed.

## Current state

#789's CI PR skips this test on React 19 only, so the new test job can land green. The skip is a `? it.skip : it` ternary rather than `it.skipIf`, because these globals are typed by `@types/jest` and `skipIf` fails `tsc` while working at runtime.

Restore it to a plain `it` when this is fixed.

Contributor guide

Open the contributing guide

Research direction

Run `npx vitest run useObservable` and start with `test/useObservable.test.tsx`, especially `emits the new observable's value if the observable is swapped out`. Trace the named `SuspenseSubject`, `preloadedObservables` cache, and `use-sync-external-store` path. Done means the test is restored to plain `it` and passes under React 19 while retaining the React 18 behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.