Skip to content

fix(tanstack-react-query): use skipToken instead of overriding enabled option#994

Open
msvargas wants to merge 1 commit into
powersync-ja:mainfrom
barvaz-engineering:fix/preserve-user-enabled-option
Open

fix(tanstack-react-query): use skipToken instead of overriding enabled option#994
msvargas wants to merge 1 commit into
powersync-ja:mainfrom
barvaz-engineering:fix/preserve-user-enabled-option

Conversation

@msvargas

Copy link
Copy Markdown
Contributor

Problem

PowerSync's useQuery and useQueries hooks override the user's enabled option by forcing enabled: streamsHaveSynced at the end of the spread. Users who need conditional query control (e.g., enabled: !!userId) lose that ability — the query always runs once streams are synced, regardless of their condition.

Additionally, two secondary issues were discovered:

  1. Stale streamsHaveSynced — The usePowerSyncQueries final useMemo was missing streamsHaveSynced in its dependency array, causing the returned value to stay stale when only streamsHaveSynced changed.

  2. Race condition on first table resolutionresolveTables is async. The change listener attaches watching [] (empty) tables, so any write landing before resolveTables resolves is silently dropped. The listener that later attaches with real tables was created after the write, so it never sees it.

Solution

Replace the enabled override pattern with TanStack's skipToken. When streams haven't synced, the queryFn is set to Tanstack.skipToken, preventing query execution without modifying the user's enabled option.

For useSuspenseQuery, skipToken is not passed (TanStack rejects it for suspense). Suspense queries always receive the real queryFn, which is correct — suspense queries must resolve.

Race condition fix: A tablesInitialized ref tracks each query's [] -> [tables] transition. On the first transition, an explicit queryClient.invalidateQueries() rescues any data that was written and dropped during the pending resolveTables window.

Changes

File Change
src/hooks/useQuery.ts Conditionally apply skipToken when streams haven't synced (and not a suspense query); remove enabled override
src/hooks/useQueries.ts Apply skipToken per-query; remove enabled override; add streamsHaveSynced to deps
src/hooks/usePowerSyncQueries.ts Add tablesInitialized ref with rescue invalidation; add streamsHaveSynced to return memo deps
tests/enabled.test.tsx Verify user enabled: false is respected for useQuery, useQueries, and useSuspenseQuery
tests/usePowerSyncQueries.test.tsx Verify streamsHaveSynced dep update + race condition rescue

Testing

All 41 tests pass (5 test files, 40 passed, 1 pre-existing skip).

@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 379fbea

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@msvargas msvargas force-pushed the fix/preserve-user-enabled-option branch 2 times, most recently from c21342d to 730b80b Compare June 15, 2026 21:53
…d option

Replace the enabled: streamsHaveSynced pattern with TanStack's skipToken to preserve the user's own enabled option.

Problems fixed:
- User's enabled option was silently overridden by PowerSync's streamsHaveSynced
- usePowerSyncQueries final useMemo was missing streamsHaveSynced in deps, returning stale values
- Race condition: change listeners attached watching [] tables while resolveTables was pending, losing first-sync writes

Changes:
- useQuery: Conditionally pass skipToken when streams haven't synced; suspense queries always get the real queryFn
- useQueries: Same skipToken approach per query entry; add streamsHaveSynced to deps
- usePowerSyncQueries: Add tablesInitialized ref to rescue data lost during the []->[tables] transition; add streamsHaveSynced to return memo deps
@msvargas msvargas force-pushed the fix/preserve-user-enabled-option branch from 730b80b to 379fbea Compare June 15, 2026 21:53
@msvargas

Copy link
Copy Markdown
Contributor Author

Hi @christiaanjourneyapps (Christiaan Landman), could you please review this fix when you have a chance? Feedback is very welcome. Also, thanks for the awesome packages! 🚀

@msvargas

Copy link
Copy Markdown
Contributor Author

Correct handle: @Chriztiaan (sorry about the typo above!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant