🧊 feat(hooks): add useCustomCompareEffect - #508
Open
ashenoooone wants to merge 1 commit into
Open
Conversation
…areEffect и useDeepEffect
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.
Fixes #507
useCustomCompareEffect(effect, deps, comparator)runs the effect when the comparator says the dependencies changed, instead of relying on React's reference check.The comparator returns
truewhen the dependencies are equal and the effect is skipped. It is read fresh on every render, so it does not belong indeps. On mount the effect always runs and the comparator is not called at all, which means it never has to handle an undefined previous value.useShallowEffectanduseDeepEffectare one-line wrappers over it, so all the dependency bookkeeping lives in one place.Calling any of them without dependencies keeps the
useEffect(fn)meaning and runs the effect on every render, matching whatuseShallowEffectdid before.Changes are tracked with a counter. The comparison runs during render, before
useEffectreads its dependencies, and every time the comparator reports a change the counter grows.What
useEffectreceives is[signalRef.current], never the caller's array. That gives two things. The array is always length 1, so a dependency list that changes length between renders never triggers React'sThe final argument passed to useEffect changed size between renderswarning. And React's own element-wiseObject.iscannot overrule the comparator, since a growing counter always reads as changed:deepEqualmoved out ofuseShallowEffect.tsintopackages/core/src/helpers/deepEqual, next to a newshallowEqual, both with tests and demo pages. The move fixes what the old implementation got wrong:NaNwas not equal to itself,DateandRegExpwere walked as plain objects so any two dates compared equal,Set,Mapand symbol keys were unhandled, a cyclic dependency overflowed the stack, andkeysB.includes(key)inside the key loop made it quadratic on wide objects.Two comparison rules are deliberate and documented in the JSDoc. Set members and Map keys compare by reference while Map values compare deeply, which matches the semantics of
SetandMapthemselves. The second rule: an object with no own enumerable string keys that is not a plain object compares by reference.Without that rule
Error,File,URL,ArrayBufferand DOM nodes all come out equal, because their state is either non-enumerable or in internal slots, anduseDeepEffect(fn, [ref.current])would never fire again. Reporting them unequal is the safe direction, since an extra run is visible and a missing one is not.Two breaking changes worth calling out.
useShallowEffectcompared dependencies recursively despite its name and now compares one level below each dependency.Callers who want the old behavior move to
useDeepEffect. And the exporteddeepEqualkeeps its name with different semantics, as described above. Both need a line in the release notes, otherwise they look like a regression appearing with no change on the caller's side.55 tests cover this, including the case where shallow and deep disagree. The demos use a nested object for the same reason: the existing
useShallowEffectdemo uses a flat one, which behaves identically under either comparator and therefore shows nothing.