fix: track visited pairs in deepEqual to avoid false positives with shared object references#13515
Merged
bluebill1049 merged 1 commit intoJun 10, 2026
Conversation
…hared object references The circular-reference guard added in react-hook-form#12914 keeps a single WeakSet of every object ever visited and returns true as soon as either side has been seen before. Equality then becomes sticky: once an object has been compared once, any later comparison involving it short-circuits to true, so plain non-circular structures that merely reuse an object reference compare as equal when they are not. Track visited (object1, object2) pairs in a WeakMap<object, WeakSet> instead. Termination for circular structures is still guaranteed because each pair is recorded before recursing and there are finitely many pairs. This is user-visible through the values prop: useForm compares the incoming values against the previous ones with deepEqual, so a rerender whose values reuse one object reference was silently ignored and the form kept stale values.
bluebill1049
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed Changes
deepEqualreturns false positives for plain, non-circular structures that reuse an object reference.Root cause: the circular-reference guard introduced in #12914 keeps one shared
WeakSetof every object ever visited and short-circuits totruewhen either side has been seen before. The first comparison ofsharedvs{ value: 1 }adds both to the set, so when the traversal reachessecond,sharedis already "visited" and{ value: 2 }is never actually compared. Equality is decided by visit history rather than by structure.This is user-visible through the
valuesprop:useFormcompares incomingvaluesagainst the previous ones withdeepEqual, so a rerender whosevaluesreuse one object reference is silently ignored and the form keeps stale values (repro included as auseFormtest).This PR tracks visited pairs (
WeakMap<object, WeakSet<object>>) instead of individual objects, which is the standard approach for cycle detection in structural equality. Termination for circular structures is still guaranteed: each(object1, object2)pair is recorded before recursing and there are finitely many pairs, so any cycle eventually revisits the same pair and short-circuits.Fixes # (no open issue; the regression was introduced by #12914)
Type of change
Checklist:
Verified locally: new unit tests + the
useFormregression test fail onmasterand pass with the fix; the existing circular-reference tests from #12914 still pass;jestondeepEqual/useForm/formState/setValuesuites → 173 passed;pnpm test:type,pnpm lint,pnpm build(incl. ESM/CJS export assertions) andpnpm api-extractorall pass;dist/index.cjs.jsstays within the bundlewatch budget (12.25 kB gzip < 12.5 kB).