Skip to content

perf(fetchium): hydrate synchronously from a sync store - #69

Merged
abedell-phantom merged 9 commits into
perf/remove-redundant-entity-savesfrom
perf/sync-hydrate-first-fetch
Oct 8, 2026
Merged

abedell-phantom merged 9 commits into
perf/remove-redundant-entity-savesfrom
perf/sync-hydrate-first-fetch

Conversation

@abedell-phantom

@abedell-phantom abedell-phantom commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Why

With a synchronous store (SyncQueryStore, such as MMKV on React Native), a query still resolved its cached value a microtask after activation. A component mounting the query rendered a loading state first, then re-rendered with the cached data. The first fetch then waited a setTimeout(0), which on React Native can take a frame or more. In the Phantom wallet this showed up as a skeleton flash and an extra render on every warm screen open.

What changed

  • With a SyncQueryStore, the cached value is applied inside the read that activates the query. The first render shows the cached data.
  • The subscription and first fetch start on a microtask instead of after setTimeout(0). Both still run outside the activating read.
  • A fetch something else already started by then (such as refetch()) is not duplicated.
  • With an AsyncQueryStore, hydration still happens when the load resolves. The subscription and fetch now follow right away, without a timer hop.

Behavior changes

  • Awaiting a query backed by a sync store resolves with the cached value immediately.
  • With stale cached data, isPending turns true for the background refetch one microtask after activation. The cached value stays readable during the refetch.
  • A cached entry that fails to apply is now treated as a cache miss, and the query fetches. Before, the query kept the entry's timestamp, and a fresh-looking timestamp could leave it pending with no fetch.
  • Changeset: patch.

Testing

  • New sync-hydration.test.ts: sync store with fresh cache, stale cache and a miss; async store pending then hydrating, and fetching after a miss without a timer hop.
  • New react/sync-hydration.test.tsx: useQuery and component() render cached data on the first render.
  • caching-persistence.test.ts updated for the earlier hydration.

Stack: #67 → #64 → #61 → #69 (this) → #70 → #71.

🤖 Generated with Claude Code

abedell-phantom and others added 2 commits October 6, 2026 13:00
… first fetch on a microtask

A query activated against SyncQueryStore (MMKV on mobile) resolved with its
cached value a microtask after activation, so the first React render showed a
loading state and a second, sync-lane render showed the data. The first fetch
then waited on setTimeout(0).

Apply the cached value in the activating read when the store returns
synchronously, and run the subscription and first fetch on a microtask, still
outside the relay's computation. AsyncQueryStore keeps its async path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lead with what changes for an app using useQuery or fetchQuery, keep
migration notes to a line or two, and drop internal detail. No source
change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@abedell-phantom
abedell-phantom force-pushed the perf/sync-hydrate-first-fetch branch from 1cc0908 to e28c770 Compare October 6, 2026 17:01
Comment thread packages/fetchium/src/QueryResult.ts
abedell-phantom and others added 6 commits October 6, 2026 19:36
invalidateQueries() sets updatedAt to 0, but isStale only treated undefined
as stale, so with staleTime: Infinity an uncached query invalidated before
its first fetch started made no request and stayed pending. An invalidation
made before the cache loaded is also kept now, instead of being overwritten
by the cached timestamp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread packages/fetchium/src/QueryResult.ts
…tely

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@abedell-phantom
abedell-phantom merged commit 0600009 into main Oct 8, 2026
1 check passed
@abedell-phantom
abedell-phantom deleted the perf/sync-hydrate-first-fetch branch October 8, 2026 00:07
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.

3 participants