Test case for potentially undesirable combo of useMemo w setState-in-render #25227
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.
Summary
This came out of an offline discussion about
useMemoCache()
(#25143) and whether, after a setState in render, the cache should reset back to that of the current fiber or continue using the wip cache. @acdlite pointed out thatuseMemo()
does the equivalent of the latter, ie reusing the wip memo cache, but I realized that this can break memoization of child components in some edge cases (ie, cause child components to rerender/recompute memoized values due to unnecessarily breaking referential equality in the parent).A render pass that does not contain a setState will always compute useMemo values relative to the current fiber: this means that if the dependencies have not changed, the computed memo value will be the same as the object that was computed and passed to children (and that thefore is present as the dependency value in their useMemo deps arrays):
However, a render pass that does have a setState will (currently) reuse the work-in-progress useMemo values. Even if the input is reset to its last committed value, dependencies have already been reset:
The current behavior is in some senses reasonable: a setState in render that happens to reset back to the same state as the last commit seems very unlikely in practice, whereas a setState in render having to redo the same (new) computation again seems likely. However, recomputing a single new value an extra time is less costly than breaking memoization caches for an entire subtree, so it's worth considering.
How did you test this change?
yarn test
(the new test is expected to fail)