Skip to content

fix(bindx-react): stabilize useEntityList selection identity to prevent refetch loop#39

Open
jindrak02 wants to merge 3 commits into
mainfrom
bug/datagrid-refetch-loop-on-unstable-children-identity
Open

fix(bindx-react): stabilize useEntityList selection identity to prevent refetch loop#39
jindrak02 wants to merge 3 commits into
mainfrom
bug/datagrid-refetch-loop-on-unstable-children-identity

Conversation

@jindrak02
Copy link
Copy Markdown

@jindrak02 jindrak02 commented May 16, 2026

Fixes #38

Problem

useEntityList re-fetched whenever the pre-resolved selection option got a fresh object identity, even with identical content and a stable queryKey. The definer path (3rd arg) was deliberately stabilized via a ref keyed on the serialized query; the options.selection path was not — selectionMeta was the raw reference and sat in the data-loading effect's dep array. Since DataGrid rebuilds selection from the children render-prop on every render, a grid nested under a re-rendering store subscriber spun into an unbounded refetch loop (/live request storm, grid flickering "No results").

Fix

Stabilize both selection paths identically.

  • useEntityList.ts — resolve the raw meta every render (from definer or options.selection), compute a selectionContentKey (serialized query), and only swap the ref'd selectionMeta when that content key changes. Both paths now share one stabilization branch, so a new-but-content-identical selection object keeps a stable selectionMeta identity and the effect doesn't refire.
  • effectiveQueryKey is derived from the stable selectionContentKey (\${entityType}:\${contentKey}) instead of re-encoding via selectionMeta, avoiding double JSON encoding.

Test

tests/react/hooks/useEntityList/selectionIdentityRefetchLoop.test.tsx — a GridLike that rebuilds a content-identical selection on a version bump while holding a stable queryKey. One deliberate identity change must not trigger a second backend round-trip (queryCount stays 1). Failed on main (Received: 2), passes here. The version-controlled identity change keeps the test from livelocking on the very loop it guards.

Test plan

  • bun test tests/react/hooks/useEntityList/selectionIdentityRefetchLoop.test.tsx — 1 pass
  • bun test tests/react/hooks/useEntityList/ — 39 pass
  • bun run typecheck — clean

jindrak02 and others added 3 commits May 16, 2026 11:47
…nt refetch loop

The options.selection path used the raw object reference for selectionMeta,
which flowed into the data-loading effect's dependency array. Callers passing
a content-identical-but-new selection object (e.g. DataGrid rebuilding its
selection when children identity changes) triggered redundant backend queries,
producing a self-sustaining /live request storm via fetch -> notify -> rerender.

Unify both selection paths (definer 3rd-arg and pre-resolved options.selection)
behind a single selectionRef keyed on the serialized query content. selectionMeta
now keeps stable identity while the query content is unchanged, and effectiveQueryKey
derives from that same content key. Legitimate refetches (changed fields without a
queryKey, changed filter/orderBy/limit/offset, or a changed queryKey) still fire.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
selectionContentKey is already the serialized query; wrapping it in another
JSON.stringify produced a double-encoded string. Namespace it by entity type
directly instead. Purely internal (effect dep / cache key) — no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@matej21 matej21 changed the title test: failing repro for useEntityList refetch on unstable pre-resolved selection identity fix(bindx-react): stabilize useEntityList selection identity to prevent refetch loop Jun 1, 2026
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.

useEntityList refetches on unstable pre-resolved selection identity (DataGrid /live request loop)

2 participants