Skip to content

Commit 0516b04

Browse files
committed
Fixed error handling for Tanstack wrapper.
Added default empty array for parameters option. Added unit tests for Tanstack wrapper.
1 parent 7e23d65 commit 0516b04

File tree

7 files changed

+184
-34
lines changed

7 files changed

+184
-34
lines changed

.changeset/brown-moose-chew.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/tanstack-react-query': patch
3+
---
4+
5+
Fixed issue with compilable queries needing a parameter value specified and fixed issue related to compilable query errors causing infinite rendering.

packages/tanstack-react-query/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"build": "tsc -b",
1616
"build:prod": "tsc -b --sourceMap false",
1717
"clean": "rm -rf lib tsconfig.tsbuildinfo",
18+
"test": "vitest",
1819
"watch": "tsc -b -w"
1920
},
2021
"repository": {
@@ -37,6 +38,7 @@
3738
"@tanstack/react-query": "^5.55.4"
3839
},
3940
"devDependencies": {
41+
"@testing-library/react": "^15.0.2",
4042
"@types/react": "^18.2.34",
4143
"jsdom": "^24.0.0",
4244
"react": "18.2.0",

packages/tanstack-react-query/src/hooks/useQuery.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { parseQuery, type CompilableQuery, type ParsedQuery, type SQLWatchOptions } from '@powersync/common';
1+
import { parseQuery, type CompilableQuery } from '@powersync/common';
22
import { usePowerSync } from '@powersync/react';
33
import React from 'react';
44

@@ -65,15 +65,10 @@ function useQueryCore<
6565
throw new Error('PowerSync is not available');
6666
}
6767

68-
const [error, setError] = React.useState<Error | null>(null);
69-
const [tables, setTables] = React.useState<string[]>([]);
70-
const { query, parameters, ...resolvedOptions } = options;
68+
let error: Error | undefined = undefined;
7169

72-
React.useEffect(() => {
73-
if (error) {
74-
setError(null);
75-
}
76-
}, [powerSync, query, parameters, options.queryKey]);
70+
const [tables, setTables] = React.useState<string[]>([]);
71+
const { query, parameters = [], ...resolvedOptions } = options;
7772

7873
let sqlStatement = '';
7974
let queryParameters = [];
@@ -85,7 +80,7 @@ function useQueryCore<
8580
sqlStatement = parsedQuery.sqlStatement;
8681
queryParameters = parsedQuery.parameters;
8782
} catch (e) {
88-
setError(e);
83+
error = e;
8984
}
9085
}
9186

@@ -97,12 +92,12 @@ function useQueryCore<
9792
const tables = await powerSync.resolveTables(sqlStatement, queryParameters);
9893
setTables(tables);
9994
} catch (e) {
100-
setError(e);
95+
error = e;
10196
}
10297
};
10398

10499
React.useEffect(() => {
105-
if (!query) return () => {};
100+
if (error || !query) return () => {};
106101

107102
(async () => {
108103
await fetchTables();
@@ -128,7 +123,7 @@ function useQueryCore<
128123
} catch (e) {
129124
return Promise.reject(e);
130125
}
131-
}, [powerSync, query, parameters, stringifiedKey, error]);
126+
}, [powerSync, query, parameters, stringifiedKey]);
132127

133128
React.useEffect(() => {
134129
if (error || !query) return () => {};
@@ -142,7 +137,7 @@ function useQueryCore<
142137
});
143138
},
144139
onError: (e) => {
145-
setError(e);
140+
error = e;
146141
}
147142
},
148143
{
@@ -151,7 +146,7 @@ function useQueryCore<
151146
}
152147
);
153148
return () => abort.abort();
154-
}, [powerSync, queryClient, stringifiedKey, tables, error]);
149+
}, [powerSync, queryClient, stringifiedKey, tables]);
155150

156151
return useQueryFn(
157152
{
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "./",
4+
"esModuleInterop": true,
5+
"jsx": "react",
6+
"rootDir": "../",
7+
"composite": true,
8+
"outDir": "./lib",
9+
"lib": ["esnext", "DOM"],
10+
"module": "esnext",
11+
"sourceMap": true,
12+
"moduleResolution": "node",
13+
"noFallthroughCasesInSwitch": true,
14+
"noImplicitReturns": true,
15+
"noImplicitUseStrict": false,
16+
"noStrictGenericChecks": false,
17+
"resolveJsonModule": true,
18+
"skipLibCheck": true,
19+
"target": "esnext"
20+
},
21+
"include": ["../src/**/*"]
22+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import * as commonSdk from '@powersync/common';
2+
import { cleanup, renderHook, waitFor } from '@testing-library/react';
3+
import React from 'react';
4+
import { beforeEach, describe, expect, it, vi } from 'vitest';
5+
import { PowerSyncContext } from '@powersync/react/';
6+
import { useQuery } from '../src/hooks/useQuery';
7+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8+
9+
const mockPowerSync = {
10+
currentStatus: { status: 'initial' },
11+
registerListener: vi.fn(() => { }),
12+
resolveTables: vi.fn(() => ['table1', 'table2']),
13+
onChangeWithCallback: vi.fn(),
14+
getAll: vi.fn(() => Promise.resolve(['list1', 'list2']))
15+
};
16+
17+
vi.mock('./PowerSyncContext', () => ({
18+
useContext: vi.fn(() => mockPowerSync)
19+
}));
20+
21+
describe('useQuery', () => {
22+
let queryClient = new QueryClient({
23+
defaultOptions: {
24+
queries: {
25+
retry: false,
26+
},
27+
}
28+
})
29+
30+
const wrapper = ({ children }) => (
31+
<QueryClientProvider client={queryClient}>
32+
<PowerSyncContext.Provider value={mockPowerSync as any}>{children}</PowerSyncContext.Provider>
33+
</QueryClientProvider>
34+
);
35+
36+
beforeEach(() => {
37+
queryClient.clear();
38+
39+
vi.clearAllMocks();
40+
cleanup(); // Cleanup the DOM after each test
41+
});
42+
43+
44+
it('should set loading states on initial load', async () => {
45+
const { result } = renderHook(() => useQuery({
46+
queryKey: ['lists'],
47+
query: 'SELECT * from lists'
48+
}), { wrapper });
49+
const currentResult = result.current;
50+
expect(currentResult.isLoading).toEqual(true);
51+
expect(currentResult.isFetching).toEqual(true);
52+
});
53+
54+
it('should execute string queries', async () => {
55+
const query = () =>
56+
useQuery({
57+
queryKey: ['lists'],
58+
query: "SELECT * from lists"
59+
});
60+
const { result } = renderHook(query, { wrapper });
61+
62+
await vi.waitFor(() => {
63+
expect(result.current.data![0]).toEqual('list1');
64+
expect(result.current.data![1]).toEqual('list2');
65+
}, { timeout: 500 });
66+
});
67+
68+
it('should execute compatible queries', async () => {
69+
const compilableQuery = {
70+
execute: () => [{ test: 'custom' }] as any,
71+
compile: () => ({ sql: 'SELECT * from lists' })
72+
} as commonSdk.CompilableQuery<any>;
73+
74+
const query = () =>
75+
useQuery({
76+
queryKey: ['lists'],
77+
query: compilableQuery
78+
});
79+
const { result } = renderHook(query, { wrapper });
80+
81+
await vi.waitFor(() => {
82+
expect(result.current.data![0].test).toEqual('custom');
83+
}, { timeout: 500 });
84+
});
85+
86+
it('should show an error if parsing the query results in an error', async () => {
87+
const compilableQuery = {
88+
execute: () => [] as any,
89+
compile: () => ({ sql: 'SELECT * from lists', parameters: ['param'] })
90+
} as commonSdk.CompilableQuery<any>;
91+
92+
const { result } = renderHook(
93+
() =>
94+
useQuery({
95+
queryKey: ['lists'],
96+
query: compilableQuery,
97+
parameters: ['redundant param']
98+
}),
99+
{ wrapper }
100+
);
101+
102+
await waitFor(
103+
async () => {
104+
const currentResult = result.current;
105+
expect(currentResult.isLoading).toEqual(false);
106+
expect(currentResult.isFetching).toEqual(false);
107+
expect(currentResult.error).toEqual(Error('You cannot pass parameters to a compiled query.'));
108+
expect(currentResult.data).toBeUndefined()
109+
},
110+
{ timeout: 100 }
111+
);
112+
});
113+
114+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { defineConfig, UserConfigExport } from 'vitest/config';
2+
3+
const config: UserConfigExport = {
4+
test: {
5+
environment: 'jsdom'
6+
}
7+
};
8+
9+
export default defineConfig(config);

pnpm-lock.yaml

Lines changed: 22 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)