Skip to content

Commit 756b2ee

Browse files
committed
fix: return new object from initialData path instead of mutating shared snapshot
useObservable was mutating the object returned by getSnapshot() in-place. That object is SuspenseSubject._immutableStatus, a single shared reference across all components using the same observableId. One component's initialData render would corrupt the snapshot for all other components. Fixes #736
1 parent 52eacea commit 756b2ee

1 file changed

Lines changed: 17 additions & 4 deletions

File tree

src/useObservable.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,24 @@ export function useObservable<T = unknown>(observableId: string, source: Observa
125125

126126
const update = useSyncExternalStore(subscribe, getSnapshot);
127127

128-
// modify the value if initialData exists
128+
// Return a new object with initialData overlaid rather than mutating the shared
129+
// _immutableStatus reference, which is the same object across all components
130+
// using the same observableId.
129131
if (!observable.hasValue && hasData) {
130-
update.data = config?.initialData ?? config?.startWithValue;
131-
update.status = 'success';
132-
update.hasEmitted = true;
132+
const initialDataValue = config?.initialData ?? config?.startWithValue;
133+
134+
// In suspense mode, throw errors so React Error Boundaries can catch them.
135+
// In non-suspense mode, surface errors via status so consumers can handle them locally.
136+
if (suspenseEnabled && update.error) {
137+
throw update.error;
138+
}
139+
140+
return {
141+
...update,
142+
data: initialDataValue,
143+
status: 'success',
144+
hasEmitted: true,
145+
} as ObservableStatus<T>;
133146
}
134147

135148
// throw an error if there is an error

0 commit comments

Comments
 (0)