Skip to content

Commit d8236aa

Browse files
authored
Fix dynamic dependency array for useMemo hook (#723)
1 parent eff8cbf commit d8236aa

File tree

3 files changed

+62
-5
lines changed

3 files changed

+62
-5
lines changed

.changeset/empty-donuts-repair.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+
Fix "order and size of this array must remain constant" warning.

packages/react/src/hooks/watched/watch-utils.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ export type InternalHookOptions<DataType> = {
1010
active: boolean;
1111
};
1212

13-
export const checkQueryChanged = <T>(query: WatchCompatibleQuery<T>, options: AdditionalOptions) => {
13+
interface WatchCompatibleQueryWithParams<T> extends WatchCompatibleQuery<T> {
14+
stringifiedParameters?: string;
15+
}
16+
17+
export const checkQueryChanged = <T>(query: WatchCompatibleQueryWithParams<T>, options: AdditionalOptions) => {
1418
let _compiled: CompiledQuery;
1519
try {
1620
_compiled = query.compile();
@@ -19,7 +23,7 @@ export const checkQueryChanged = <T>(query: WatchCompatibleQuery<T>, options: Ad
1923
}
2024
const compiled = _compiled!;
2125

22-
const stringifiedParams = JSON.stringify(compiled.parameters);
26+
const stringifiedParams = query.stringifiedParameters ?? JSON.stringify(compiled.parameters);
2327
const stringifiedOptions = JSON.stringify(options);
2428

2529
const previousQueryRef = React.useRef({ sqlStatement: compiled.sql, stringifiedParams, stringifiedOptions });
@@ -45,15 +49,18 @@ export const constructCompatibleQuery = <RowType>(
4549
options: AdditionalOptions
4650
) => {
4751
const powerSync = usePowerSync();
52+
const stringifiedParameters = React.useMemo(() => JSON.stringify(parameters), [parameters]);
4853

49-
const parsedQuery = React.useMemo<WatchCompatibleQuery<RowType[]>>(() => {
54+
const parsedQuery = React.useMemo<WatchCompatibleQueryWithParams<RowType[]>>(() => {
5055
if (typeof query == 'string') {
5156
return {
5257
compile: () => ({
5358
sql: query,
5459
parameters
5560
}),
56-
execute: () => powerSync.getAll(query, parameters)
61+
execute: () => powerSync.getAll(query, parameters),
62+
// Setting this is a small optimization that avoids checkQueryChanged recomputing the JSON representation.
63+
stringifiedParameters
5764
};
5865
} else {
5966
return {
@@ -66,9 +73,11 @@ export const constructCompatibleQuery = <RowType>(
6673
};
6774
},
6875
execute: () => query.execute()
76+
// Note that we can't set stringifiedParameters here because we only know parameters after the query has been
77+
// compiled.
6978
};
7079
}
71-
}, [query, powerSync, ...parameters]);
80+
}, [query, powerSync, stringifiedParameters]);
7281

7382
const queryChanged = checkQueryChanged(parsedQuery, options);
7483

packages/react/tests/useQuery.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,49 @@ describe('useQuery', () => {
596596
);
597597
});
598598

599+
it('sohuld allow changing parameter array size', async () => {
600+
const db = openPowerSync();
601+
602+
let currentQuery = { sql: 'SELECT ? AS a', params: ['foo'] };
603+
let listeners: (() => void)[] = [];
604+
605+
const query = () => {
606+
const current = React.useSyncExternalStore(
607+
(onChange) => {
608+
listeners.push(onChange);
609+
return () => listeners.splice(listeners.indexOf(onChange), 1);
610+
},
611+
() => currentQuery
612+
);
613+
614+
return useQuery(current.sql, current.params);
615+
};
616+
617+
const { result } = renderHook(query, { wrapper: ({ children }) => testWrapper({ children, db }) });
618+
619+
await vi.waitFor(
620+
() => {
621+
expect(result.current.data).toStrictEqual([{ a: 'foo' }]);
622+
},
623+
{ timeout: 500, interval: 50 }
624+
);
625+
626+
// Now update the parameter
627+
act(() => {
628+
currentQuery = { sql: 'SELECT ? AS a, ? AS b', params: ['foo', 'bar'] };
629+
for (const listener of listeners) {
630+
listener();
631+
}
632+
});
633+
634+
await vi.waitFor(
635+
() => {
636+
expect(result.current.data).toStrictEqual([{ a: 'foo', b: 'bar' }]);
637+
},
638+
{ timeout: 500, interval: 50 }
639+
);
640+
});
641+
599642
it('should show an error if parsing the query results in an error', async () => {
600643
const db = openPowerSync();
601644

0 commit comments

Comments
 (0)