Skip to content

fix(svelte-store): use $state.raw in useSelector to prevent proxy equality mismatch#334

Open
seongwon030 wants to merge 1 commit into
TanStack:mainfrom
seongwon030:fix/svelte-store-use-selector-proxy-equality
Open

fix(svelte-store): use $state.raw in useSelector to prevent proxy equality mismatch#334
seongwon030 wants to merge 1 commit into
TanStack:mainfrom
seongwon030:fix/svelte-store-use-selector-proxy-equality

Conversation

@seongwon030
Copy link
Copy Markdown

@seongwon030 seongwon030 commented May 22, 2026

🎯 Changes

Fixes #322

$state() wraps object values in a Svelte Proxy. When selector(s) returns the same
raw object reference, === comparison with the Proxy always fails — triggering
state_proxy_equality_mismatch warnings and unnecessary re-renders even when the
selected value hasn't changed.

Existing tests only covered primitive selectors, which are not Proxy-wrapped. A new test
with an object selector reproduces the issue and verifies the fix.

Switching to $state.raw() is safe since useSelector only ever reassigns slice,
never mutates its properties.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes
    • Prevented unnecessary re-renders and runtime warnings when store selections remain unchanged by improving selector equality detection.
  • Tests
    • Added tests that verify components do not re-render when unrelated store fields update.
  • Chores
    • Added release metadata documenting the fix.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33b74a6a-999c-4733-a897-4279c299726b

📥 Commits

Reviewing files that changed from the base of the PR and between 050f9b9 and 637447a.

📒 Files selected for processing (4)
  • .changeset/fix-svelte-store-proxy-equality.md
  • packages/svelte-store/src/useSelector.svelte.ts
  • packages/svelte-store/tests/ProxyEquality.test.svelte
  • packages/svelte-store/tests/index.test.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/fix-svelte-store-proxy-equality.md

📝 Walkthrough

Walkthrough

This PR fixes a state_proxy_equality_mismatch in useSelector by replacing $state() with $state.raw() for the internal slice variable; adds a changeset and tests that verify ignored updates do not trigger re-renders.

Changes

useSelector Svelte proxy equality fix

Layer / File(s) Summary
useSelector $state.raw() fix and changeset
packages/svelte-store/src/useSelector.svelte.ts, .changeset/fix-svelte-store-proxy-equality.md
Core change: initialize the selector slice with $state.raw(selector(source.get())) instead of $state(...), preventing Svelte proxy wrapping; changeset documents the patch fix.
Proxy equality test component and integration
packages/svelte-store/tests/ProxyEquality.test.svelte, packages/svelte-store/tests/index.test.ts
Adds a Svelte test component that derives state via useSelector, tracks renderCount, and a test asserting that updating an unrelated ignored field does not increment render count when the selector returns the same object reference.

🎯 2 (Simple) | ⏱️ ~10 minutes

🐰 A proxy wrapped in raw delight,
Equality checks now shining bright!
No more warnings in the night,
useSelector renders just right! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: using $state.raw() in useSelector to fix proxy equality mismatches.
Description check ✅ Passed The description is comprehensive, addressing the root cause, symptoms, and solution with proper checklist completion.
Linked Issues check ✅ Passed All changes directly address issue #322 by switching to $state.raw() for slice initialization, adding tests for object selectors, and including a changeset.
Out of Scope Changes check ✅ Passed All changes are scoped to fix issue #322: slice initialization, test coverage for object selectors, and supporting changeset entry.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: lockfile failed supply-chain policy check. Run pnpm install locally to update the lockfile.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/svelte-store/src/useSelector.svelte.ts (1)

38-38: ⚡ Quick win

Consider adding an inline comment to explain the use of $state.raw().

Future maintainers might wonder why $state.raw() is used instead of $state(). A brief comment explaining that raw prevents proxy wrapping and ensures correct equality checks would improve maintainability.

📝 Suggested comment
   const compare = options.compare ?? defaultCompare
-  let slice = $state.raw(selector(source.get()))
+  // Use $state.raw() to prevent Svelte Proxy wrapping, ensuring === comparison
+  // with raw selector output works correctly (prevents state_proxy_equality_mismatch)
+  let slice = $state.raw(selector(source.get()))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/svelte-store/src/useSelector.svelte.ts` at line 38, Add a brief
inline comment above the expression using $state.raw(selector(source.get()))
explaining why .raw() is used (to bypass proxy wrapping so selector equality
checks compare plain values rather than proxies), e.g., reference the $state.raw
call and the selector/source.get usage in the same line so maintainers see that
raw prevents proxy-wrapped results from breaking equality checks in the slice
variable assignment.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/svelte-store/src/useSelector.svelte.ts`:
- Line 38: Add a brief inline comment above the expression using
$state.raw(selector(source.get())) explaining why .raw() is used (to bypass
proxy wrapping so selector equality checks compare plain values rather than
proxies), e.g., reference the $state.raw call and the selector/source.get usage
in the same line so maintainers see that raw prevents proxy-wrapped results from
breaking equality checks in the slice variable assignment.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da0d8182-7e6e-40ae-aa67-b03109e64bfb

📥 Commits

Reviewing files that changed from the base of the PR and between 86251e5 and 050f9b9.

📒 Files selected for processing (4)
  • .changeset/fix-svelte-store-proxy-equality.md
  • packages/svelte-store/src/useSelector.svelte.ts
  • packages/svelte-store/tests/ProxyEquality.test.svelte
  • packages/svelte-store/tests/index.test.ts

@seongwon030 seongwon030 force-pushed the fix/svelte-store-use-selector-proxy-equality branch from 050f9b9 to 637447a Compare May 22, 2026 14:21
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.

useSelector triggers state_proxy_equality_mismatch on every store update in svelte-store

1 participant