Skip to content

Key collection subscriptions by query identity#22

Merged
TrystonPerry merged 5 commits into
masterfrom
tdawg/query-key-resub-flash-v2
Jun 16, 2026
Merged

Key collection subscriptions by query identity#22
TrystonPerry merged 5 commits into
masterfrom
tdawg/query-key-resub-flash-v2

Conversation

@TrystonPerry

Copy link
Copy Markdown
Collaborator

Summary

  • Key useCollection listener reuse by Firestore query identity with queryEqual, while preserving rebuilds for real query changes.
  • Share collection query assembly through buildCollectionQuery so comparison and subscription paths stay aligned.
  • Document the new behavior and add regression tests for constraint reference churn and gated empty in queries.

Tests

  • pnpm typecheck
  • pnpm test

… keying

Approach v1 (caller-supplied key).

When provided, the subscription is keyed on queryKey instead of the
queryConstraints array reference, so upstream reference churn (e.g. arrays
read from deep-cloned documents) does not tear down and reload the listener.
Latest constraints are read through a ref at rebuild time.

- queryKey?: string on UseCollectionOptions
- regression tests in src/hooks.test.ts (react-test-renderer)
- README / docs / AGENTS.md contract updates
… drop queryKey

Approach v2 (auto-stabilization, no public API).

Replaces the caller-supplied queryKey with automatic stabilization: the hook
builds the query and compares it with Firestore's own queryEqual, keeping the
previous constraints reference when the query is unchanged. Reference churn
(e.g. constraint inputs read from a deep-cloned document) no longer tears down
the listener, and a genuine query change still rebuilds it — with no caller
key and no silent-staleness footgun.

- removes queryKey from UseCollectionOptions (public API)
- queryConstraintsEqual()/buildCollectionQuery() helpers in hooks.ts
- tests rewritten to assert semantic-identity behavior (real Firestore +
  real query/queryEqual, onSnapshot mocked)
- README / docs / AGENTS.md updated; callers no longer need to memoize
V2's queryEqual subscription stabilization duplicated collection.ts's
constraint merge order (definition constraints first, then hook-level,
bare collection ref when empty). The two copies had to stay in sync by
hand — drift would let the comparator keep a stale listener or
reintroduce the resubscribe flash, with nothing to catch it.

Extract one exported buildCollectionQuery() in collection.ts and route
both the live subscription and useCollection's queryEqual comparison
through it. The merge order now exists in exactly one place.

No behavior change. Typecheck clean; 142/142 tests pass.
…constraints

queryConstraintsEqual built both the prior and incoming constraint
arrays to compare them with queryEqual. For lazy collections, the hook
becomes active (enabled + resolved path) before load() attaches a
listener, so a render carrying a gated placeholder like
where(documentId(), 'in', []) followed by real IDs would build the
stale empty-array query during the identity compare and throw, crashing
the render before load() ran.

Wrap the build in try/catch: if the prior snapshot can't be built, no
live listener could have run it, so treat the snapshots as unequal and
adopt the new constraints. Defends against any unbuildable constraint
shape, not just the lazy empty-in case.
@TrystonPerry TrystonPerry merged commit 8bdcefc into master Jun 16, 2026
1 check passed
@TrystonPerry TrystonPerry deleted the tdawg/query-key-resub-flash-v2 branch June 16, 2026 22:19
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