From 48dc2cb329f88720dc7c6b792ef68c18b33b0318 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Mon, 10 Mar 2025 16:16:43 +0200 Subject: [PATCH 1/2] Fix useQuery race condition for initial query. --- .changeset/slimy-glasses-jog.md | 6 ++++++ packages/react/src/hooks/useQuery.ts | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 .changeset/slimy-glasses-jog.md diff --git a/.changeset/slimy-glasses-jog.md b/.changeset/slimy-glasses-jog.md new file mode 100644 index 000000000..d884f3616 --- /dev/null +++ b/.changeset/slimy-glasses-jog.md @@ -0,0 +1,6 @@ +--- +'@powersync/react': patch +'@powersync/react-native': patch +--- + +Fixed an issue with `useQuery` where initial query/parameter changes could cause a race condition if the first query took long. diff --git a/packages/react/src/hooks/useQuery.ts b/packages/react/src/hooks/useQuery.ts index 3559928cd..f840a9fa1 100644 --- a/packages/react/src/hooks/useQuery.ts +++ b/packages/react/src/hooks/useQuery.ts @@ -95,11 +95,16 @@ export const useQuery = ( setError(wrappedError); }; - const fetchData = async () => { + const fetchData = async (signal?: AbortSignal) => { setIsFetching(true); try { const result = typeof query == 'string' ? await powerSync.getAll(sqlStatement, queryParameters) : await query.execute(); + + if (signal?.aborted) { + return; + } + handleResult(result); } catch (e) { console.error('Failed to fetch data:', e); @@ -118,9 +123,10 @@ export const useQuery = ( }; React.useEffect(() => { + const abortController = new AbortController(); const updateData = async () => { await fetchTables(); - await fetchData(); + await fetchData(abortController.signal); }; updateData(); @@ -129,7 +135,10 @@ export const useQuery = ( schemaChanged: updateData }); - return () => l?.(); + return () => { + abortController.abort(); + l?.(); + }; }, [powerSync, memoizedParams, sqlStatement]); React.useEffect(() => { From 803f274cf803a4de41901c3e24c32c92e9d5d125 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Mon, 10 Mar 2025 16:51:41 +0200 Subject: [PATCH 2/2] Also passing relevant signal to fetchTables and when fetching data from the watched query part of the hook. --- packages/react/src/hooks/useQuery.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/react/src/hooks/useQuery.ts b/packages/react/src/hooks/useQuery.ts index f840a9fa1..b5701027d 100644 --- a/packages/react/src/hooks/useQuery.ts +++ b/packages/react/src/hooks/useQuery.ts @@ -20,7 +20,7 @@ export type QueryResult = { /** * Function used to run the query again. */ - refresh?: () => Promise; + refresh?: (signal?: AbortSignal) => Promise; }; /** @@ -112,9 +112,14 @@ export const useQuery = ( } }; - const fetchTables = async () => { + const fetchTables = async (signal?: AbortSignal) => { try { const tables = await powerSync.resolveTables(sqlStatement, memoizedParams, memoizedOptions); + + if (signal?.aborted) { + return; + } + setTables(tables); } catch (e) { console.error('Failed to fetch tables:', e); @@ -125,7 +130,7 @@ export const useQuery = ( React.useEffect(() => { const abortController = new AbortController(); const updateData = async () => { - await fetchTables(); + await fetchTables(abortController.signal); await fetchData(abortController.signal); }; @@ -150,7 +155,7 @@ export const useQuery = ( powerSync.onChangeWithCallback( { onChange: async () => { - await fetchData(); + await fetchData(abortController.current.signal); }, onError(e) { handleError(e);