Value equality does not handle Temporals
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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