Skip to content

fix: use Object.is in replaceEqualDeep for correct NaN handling#10329

Open
alex-js-ltd wants to merge 3 commits intoTanStack:mainfrom
alex-js-ltd:fix/replace-equal-deep-nan-comparison
Open

fix: use Object.is in replaceEqualDeep for correct NaN handling#10329
alex-js-ltd wants to merge 3 commits intoTanStack:mainfrom
alex-js-ltd:fix/replace-equal-deep-nan-comparison

Conversation

@alex-js-ltd
Copy link

@alex-js-ltd alex-js-ltd commented Mar 24, 2026

🎯 Changes

replaceEqualDeep previously used === for equality checks.

This causes an issue with NaN, since NaN === NaN is false. As a result, values containing NaN are treated as unequal, preventing structural sharing and causing unnecessary object replacement.

This PR replaces === with Object.is, which correctly treats:

  • NaN as equal to NaN
  • +0 and -0 as distinct

A test has been added to verify that objects containing NaN preserve reference equality.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run 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

    • Improved deep-equality logic so special numeric cases (notably NaN) are treated as equal, improving caching, comparisons, and reference preservation across nested structures.
  • Tests

    • Added unit tests covering NaN in nested objects and arrays to ensure correct equality behavior and stable reference reuse.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 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: a9bfcb92-466a-4640-a1c5-3e3fb0480866

📥 Commits

Reviewing files that changed from the base of the PR and between 9a33a68 and d5fb45b.

📒 Files selected for processing (1)
  • packages/query-core/src/__tests__/utils.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/query-core/src/tests/utils.test.tsx

📝 Walkthrough

Walkthrough

Replaced === with Object.is in replaceEqualDeep to treat NaN as equal; added unit tests for NaN-related cases and a changeset documenting a patch release for @tanstack/query-core.

Changes

Cohort / File(s) Summary
Changeset Documentation
.changeset/happy-banks-repeat.md
Adds a changeset entry declaring a patch release for @tanstack/query-core describing the replaceEqualDeep fix.
Core Implementation
packages/query-core/src/utils.ts
Replaces === with Object.is in replaceEqualDeep for top-level and recursive comparisons, altering equality semantics for NaN and signed zero.
Test Suite
packages/query-core/src/__tests__/utils.test.tsx
Adds tests asserting reference preservation and NaN handling (including replaceEqualDeep(NaN, NaN) and nested/array cases).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nibble code beneath the moonlit stand,

NaN finds itself a friend at hand.
Object.is hops in, switches the tune,
Deep trees stay cozy, same shapes in noon.
A tiny patch — a carrot for this land.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: replacing === with Object.is in replaceEqualDeep specifically to fix NaN handling.
Description check ✅ Passed The description follows the template structure with all required sections completed: Changes section explains the problem and solution, Checklist confirms testing and contributing guide compliance, and Release Impact confirms changeset generation.

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

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

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
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/query-core/src/__tests__/utils.test.tsx (1)

419-424: Good test coverage for the NaN fix.

The test correctly validates that structural sharing works when objects contain NaN values.

For completeness, consider adding tests for:

  • Top-level primitives: expect(replaceEqualDeep(NaN, NaN)).toBe(NaN)
  • Arrays containing NaN: expect(replaceEqualDeep([NaN], [NaN])).toBe(prev)
🧪 Additional test cases
     it('should return the previous object reference when values contain NaN', () => {
       const prev = { value: NaN }
       const next = { value: NaN }
       expect(replaceEqualDeep(prev, next)).toBe(prev)
     })
+
+    it('should return the previous value when both values are NaN', () => {
+      expect(Number.isNaN(replaceEqualDeep(NaN, NaN))).toBe(true)
+    })
+
+    it('should return the previous array reference when values contain NaN', () => {
+      const prev = [NaN, { a: 1 }]
+      const next = [NaN, { a: 1 }]
+      expect(replaceEqualDeep(prev, next)).toBe(prev)
+    })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/query-core/src/__tests__/utils.test.tsx` around lines 419 - 424, Add
two more tests for replaceEqualDeep: one that verifies top-level primitives with
NaN return the same primitive (use expect(replaceEqualDeep(NaN,
NaN)).toBe(NaN)), and one that verifies arrays with NaN preserve structural
sharing (create const prev = [NaN]; const next = [NaN];
expect(replaceEqualDeep(prev, next)).toBe(prev)). Ensure both tests live
alongside the existing NaN test and reference the replaceEqualDeep function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/query-core/src/__tests__/utils.test.tsx`:
- Around line 419-424: Add two more tests for replaceEqualDeep: one that
verifies top-level primitives with NaN return the same primitive (use
expect(replaceEqualDeep(NaN, NaN)).toBe(NaN)), and one that verifies arrays with
NaN preserve structural sharing (create const prev = [NaN]; const next = [NaN];
expect(replaceEqualDeep(prev, next)).toBe(prev)). Ensure both tests live
alongside the existing NaN test and reference the replaceEqualDeep function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35d095f1-e907-44e9-90e4-f2bd81f742ab

📥 Commits

Reviewing files that changed from the base of the PR and between 1047cdc and 9a33a68.

📒 Files selected for processing (3)
  • .changeset/happy-banks-repeat.md
  • packages/query-core/src/__tests__/utils.test.tsx
  • packages/query-core/src/utils.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant