TanStack / TanStack/form

Value equality does not handle Temporals

Open
#2,195 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

v1 v2
Dominant language
TypeScript
Stars
6.7k
Forks
682
Avg merge
5d 18h
Merged PRs (30d)
7

Description

Describe the bug

The evaluate method in form-core, which does deep equality checking, does not work for Temporals. Previously, it always treated two Temporal instances (e.g., two Temporal.PlainDate) as equal, because they have no enumerable properties. With the recent fix #2140, it will now always treat temporal instances as not equal.

The new behavior is clearly better, but it's still incorrect. A date field backed by a Temporal instance will be considered "dirty" even if the user resets it back to the original date.

evaluate already special-cases some built-in types, like Date. With Temporal advancing to Stage 4 and shipping in browsers, it might make sense to special-case them as well.

OTOH, given that people will be using polyfills for a while yet, it may be best to duck-type. Here's one snippet that could be added to evaluate:

const objAToStringTag = objA[Symbol.toStringTag];
if (objAToStringTag.startsWith('Temporal.') && objAToStringTag === objB[Symbol.toStringTag]) {
  // Handle Temporal types (they all have .equals())
  return objA.equals(objB);
}
Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-1zaxj72u?file=src%2Fmain.ts

Steps to reproduce

The simple repro is:

evaluate(Temporal.PlainDate.from('2026-01-01'), Temporal.PlainDate.from('2026-01-01')) === false

I think any form that has a field with a Temporal value (e.g., a Temporal.PlainDate instance) will be subject to dirty-checking issues because of this.

Expected behavior
evaluate(Temporal.PlainDate.from('2026-01-01'), Temporal.PlainDate.from('2026-01-01')) === true
How often does this bug happen?

None

Screenshots or Videos

No response

Platform

This should be universal!

TanStack Form adapter

None

TanStack Form version

1.32.1

TypeScript version

No response

Additional context

Currently we are using this patch (pnpm patch) of @tanstack/form-core. It's a bit more general, and a bit less safe, because it just checks for the .equals() method. But it has the benefit of working for other types, like Decimal.js.

diff --git a/dist/cjs/utils.cjs b/dist/cjs/utils.cjs
--- a/dist/cjs/utils.cjs
+++ b/dist/cjs/utils.cjs
@@ -198,6 +198,10 @@ function evaluate(objA, objB) {
   if (objA instanceof Date && objB instanceof Date) {
     return objA.getTime() === objB.getTime();
   }
+  // Handle Temporal types (they all have .equals())
+  if (typeof objA.equals === "function" && objA.constructor === objB.constructor) {
+    return objA.equals(objB);
+  }
   if (objA instanceof Map && objB instanceof Map) {
     if (objA.size !== objB.size) return false;
     for (const [k, v] of objA) {
diff --git a/dist/esm/utils.js b/dist/esm/utils.js
--- a/dist/esm/utils.js
+++ b/dist/esm/utils.js
@@ -196,6 +196,10 @@ function evaluate(objA, objB) {
   if (objA instanceof Date && objB instanceof Date) {
     return objA.getTime() === objB.getTime();
   }
+  // Handle Temporal types (they all have .equals())
+  if (typeof objA.equals === "function" && objA.constructor === objB.constructor) {
+    return objA.equals(objB);
+  }
   if (objA instanceof Map && objB instanceof Map) {
     if (objA.size !== objB.size) return false;
     for (const [k, v] of objA) {

I wish JavaScript had a convention like for a method that checks value equality, like Symbol.for('valueEquals') or something

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the form-core evaluate method referenced in the issue and reproduce the Temporal.PlainDate equality example. Read the existing Date and other built-in comparisons, then verify that equivalent and different Temporal values produce the expected results and that form dirty-checking follows them. No test file is named, so add or run the relevant evaluate tests if the surrounding test structure identifies them.

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
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.