Skip to content

Commit 1bbba11

Browse files
authored
(fix): useQuery result is incomplete when waiting on stream (#897)
1 parent b90ac09 commit 1bbba11

3 files changed

Lines changed: 40 additions & 1 deletion

File tree

.changeset/dirty-pumas-change.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/react': patch
3+
---
4+
5+
Fixed an issue where if a `useQuery` hook was waiting for a sync stream, the result sometimes omitted fields that should always be present according to the TypeScript typing.

packages/react/src/hooks/watched/useWatchedQuery.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,12 @@ export const useWatchedQuery = <RowType = unknown>(
8282

8383
const shouldReportCurrentlyFetching = (hookOptions.reportFetching ?? true) && !!disposePendingUpdateListener.current;
8484
const result = useNullableWatchedQuerySubscription(watchedQuery);
85+
86+
// Result is only undefined when there is no data available yet, defaults are defined accordingly
8587
return {
86-
...result,
88+
data: result?.data ?? [],
89+
isLoading: result?.isLoading ?? true,
90+
error: result?.error,
8791
isFetching: result?.isFetching || shouldReportCurrentlyFetching
8892
};
8993
};

packages/react/tests/streams.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,35 @@ describe('stream hooks', () => {
9393
await waitFor(() => expect(result.current.data).toHaveLength(1), { timeout: 1000, interval: 100 });
9494
});
9595

96+
it('useQuery returns complete result shape during stream sync transition', async () => {
97+
const allResults: any[] = [];
98+
99+
const { result } = renderHook(
100+
() => {
101+
const queryResult = useQuery('SELECT 1', [], { streams: [{ name: 'a', waitForStream: true }] });
102+
allResults.push({ ...queryResult });
103+
return queryResult;
104+
},
105+
{ wrapper: testWrapper }
106+
);
107+
108+
// Initial state should be loading
109+
expect(result.current).toMatchObject({ isLoading: true });
110+
await waitFor(() => expect(currentStreams()).toHaveLength(1), { timeout: 1000, interval: 100 });
111+
112+
// Trigger sync — this causes streamsHaveSynced to transition to true
113+
db.currentStatus = _testStatus;
114+
db.iterateListeners((l) => l.statusChanged?.(_testStatus));
115+
116+
// Wait for the query to eventually resolve
117+
await waitFor(() => expect(result.current.data).toHaveLength(1), { timeout: 1000, interval: 100 });
118+
119+
// Every intermediate render result should have the complete shape
120+
for (const r of allResults) {
121+
expect(r).toMatchObject({ data: expect.any(Array), isLoading: expect.any(Boolean), isFetching: expect.any(Boolean) });
122+
}
123+
});
124+
96125
it('useQuery not waiting on stream', async () => {
97126
// By default, it should still run the query immediately instead of waiting for the stream to resolve.
98127
const { result } = renderHook(() => useQuery('SELECT 1', [], { streams: [{ name: 'a' }] }), {
@@ -177,3 +206,4 @@ const _testStatus = new SyncStatus({
177206
]
178207
}
179208
});
209+

0 commit comments

Comments
 (0)