trackQuery.entered re-fires when an unrelated query key changes
- Dominant language
- TypeScript
- Stars
- 8
- Forks
- 2
- Avg merge
- 13h 44m
- Merged PRs (30d)
- 1
Description
### Package
@effector/router (core)
### What happened?
`trackQuery`'s `entered` event (and `$state`) re-fires on **every** `$query` update while the tracker is eligible (its `routes` match the active route) — even when none of the schema-owned keys actually changed. A chain sampled off `tracker.entered` therefore re-runs whenever an unrelated query parameter changes.
### Root cause
In `packages/core/lib/track-query.ts`:
```ts
const $result = combine($eligible, $query, (eligible, query) =>
eligible ? parameters.safeParse(query) : null,
);
```
`safeParse` returns a new object reference on every call, so `combine` treats $result (and the derived $evaluation) as changed on any $query update, regardless of whether the schema-relevant fields differ from the previous parse. The downstream sample that fires `entered` has no equality guard, so it re-emits with logically identical parsed params.
### Reproduction
```ts
const home = createRoute({ path: '/' });
const controls = createRouterControls();
const router = createRouter({ routes: [home], controls });
const scope = fork();
await allSettled(router.setHistory, { scope, params: historyAdapter(createMemoryHistory({ initialEntries: ['/'] })) });
const tracker = trackQuery({
controls,
routes: [home],
parameters: z.object({ id: z.string() }),
});
const enteredCalls = watchCalls(tracker.entered, scope);
await allSettled(router.navigate, { scope, params: { path: '/', query: { id: '1' } } });
// enteredCalls: 1
await allSettled(router.navigate, { scope, params: { path: '/', query: { id: '1', tab: 'details' } } });
// enteredCalls: 2 (unchanged id, unrelated key added)
await allSettled(router.navigate, { scope, params: { path: '/', query: { id: '1', tab: 'other' } } });
// enteredCalls: 3 (unrelated key changed again)
```
Expected: `enteredCalls` stays at 1 across both extra navigations, since `id` never changes.
Actual: fires again on every unrelated query mutation.
### Suggested direction
Add an equality guard on the parsed result (e.g. `updateFilter`/deep-equal on `result.data`, or compare only schema-owned keys) before deriving `entered`/`$state`, so unrelated query keys don't cause spurious re-entry.
### Traceability
- Re-scoped from #35, which asked to re-evaluate the original atomic-router over-triggering report (atomic-router/atomic-router#74) against the current `trackQuery` API.
- Confirmed: route-level scoping via `routes` works correctly (see `packages/core/tests/track-query.test.ts` — "for routes"), but query-key-level scoping does not — this is the real remaining case.
Contributor guide
Research direction
Begin with packages/core/lib/track-query.ts, focusing on the $result and derived $evaluation flow, then read the existing track-query tests in packages/core/tests/track-query.test.ts. Reproduce the three navigations from the issue and verify that changing unrelated query keys does not fire entered or alter $state when the schema-owned id is unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100