fix: auto-scroll bug with multiple containers #1461
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.
In April 2023,
akantic
identified an auto-scroll issue withdnd-kit
in #1092 and then proposed a fix for it in #1094. In September 2023, unaware ofakantic
's PR, I fixed the same issue in CODAP in CODAP #901 with a different local patch that has similar effect. In comparing the two different fixes, I now believe thatakantic
's fix is better than the one that I came up with, but that it can still be improved upon, as demonstrated in this PR.The code in question comes from the
useRects()
hook, which is responsible for maintaining/returning an array of rectangles that correspond to the bounding rectangles of an array of elements passed in as an argument. (In the context in which it is used, this array of elements is returned from a call touseScrollableAncestors()
.) The code in question is:where
measureRects()
is the dispatcher returned from a call to theuseReducer()
hook that is responsible for updating the state to the correct array of bounding rectangles. The current code guarantees thatmeasureRects()
will be called on transitions to/from an emptyelements
array. The bug occurs when, for instance, on subsequent calls to theuseRects()
hook theelements
array changes from one non-zero length to another. In this case,measureRects()
is not called and so the returned array of bounding rectangles has the wrong length. In the words of #1094, "Then, this code brings chaos," pointing to theuseAutoScroller()
hook which indexes into the array of rectangles.akantic
's fix was to move the call tomeasureRects()
in theuseIsomorphicLayoutEffect()
hook outside of theelse
to the root of the effect so that it would be called whenever the hook ran, independent of whether theelements
array was empty or not. This makes sense, given that any time the array of elements changes, we need to update the array of bounding rectangles.My original fix was to change the conditional that precedes the effect to:
This has similar effect, in that
measureRects()
is called any time the length of theelements
array changes, but in this case it is called at the root of the render function outside of any effects. While this fixes the issue we were encountering in which the length of theelements
array was changing, a limitation of this fix is that it doesn't handle the case in which theelements
array changes without changing length, which is whyakantic
's fix is preferable.Neither of these earlier fixes accounts for the fact that the other purpose of the
useIsomorphicLayoutEffect()
hook is to connect/disconnect a resize observer to/from each of the elements in the array, however. Thus, with either of our fixes it is possible for the code to continue to observe elements that are no longer present in theelements
array.This PR implements what I believe to be an improved fix which replaces the original code with the simpler:
The transition to/from an empty
elements
array is no longer a special case. Any time theelements
array changes, we must disconnect the resize observer from the previous elements, callmeasureRects()
to update the current set of bounding rectangles, and make sure that the current elements are observed. Doing so consistently inside theuseIsomorphicLayoutEffect()
obviates the need for the separate conditional call tomeasureRects()
outside the effect.See also:
akantic
's original issueakantic
's original PR