Skip to content

Commit f6fc807

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 6c015ca commit f6fc807

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
@@ -124,11 +124,24 @@ export function useObservable<T = unknown>(observableId: string, source: Observa
124124

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

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

134147
// In suspense mode, throw errors so React Error Boundaries can catch them.

0 commit comments

Comments
 (0)