Bug fix: Changing scrollElement prop on multiple WindowScrollers doesn't mount new scroll handlers #1660
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.
Hey guys,
I noticed there was a bug with the
WindowScroller
which stopped it from properly mounting the scroll and resize handlers to a new scrollElement if it is passed in via props. This is also considerably worse with multipleWindowScroller
s and I want to fix that too.Example: Single WindowScroller but we change the scrollElement from window to a div.
https://codepen.io/willhua/pen/zYZwygO?editors=1111
The current workaround I have seen people using is to have a
key
prop on theWindowScroller
which forcibly remounts the whole component therefore bypassing the logic in thecomponentDidUpdate
lifecycle handler which normally deals with unmounting the old handlers and mounting the new ones.E.g.
I noticed this issue also happens with multiple
WindowScroller
s since when you change thescrollElement
on multipleWindowScroller
s this series of events occurs:scrollElement
because the props on the Components have already changed so this condition fails. This is most likely because we simply store references to the components themselves inmountedInstances
mountedInstances
(i.e.!mountedInstances.length
is never true)The problem lies here in unregisterScrollListener:
react-virtualized/source/WindowScroller/utils/onScroll.js
Line 84 in abe0530
The problem in this particular example is that we remove WindowScroller 1 then immediately add it back to that array straight away in
componentDidUpdate
on line 158:react-virtualized/source/WindowScroller/WindowScroller.js
Lines 146 to 163 in abe0530
Once we get around to updating WindowScroller 2 we fail this condition in
unregisterScrollListener
and never unmount the old listeners:react-virtualized/source/WindowScroller/utils/onScroll.js
Line 87 in abe0530
I also noticed that with multiple
WindowScroller
s we weren't unmounting the handlers provided fromdetectElementResize.js
since thescrollListener
reference we are attempting to unmount is not the same reference as the one used to mount it (i.e. we have a closure which builds ascrollListener
function then we attempt to unmount from a different closure with a newscrollListener
function).See:
react-virtualized/source/vendor/detectElementResize.js
Line 194 in abe0530
Also:
react-virtualized/source/vendor/detectElementResize.js
Line 224 in abe0530
The fixes:
onScroll.js
- Instead of just storing a reference to a Component, we store the Component along with the Element we bound the listeners to{ component, element }
. That way when we change the Component (the props update before we get into theregisterScrollListener
andunregisterScrollListener
calls which is why they fail at the moment) we instead avoid the prop comparison which is unsafe and failure prone and instead compare against the Element reference we stored against the Component and if there are no knownWindowScroller
s registered against the component we remove the listener.detectElementResize.js
- This fails to remove the scroll listener attached to the element if there are multiple instances of the closure and in our case if there is more than 1WindowScroller
then we have multiple instances. The problem that occurs here is that if we unmount one of the otherWindowScroller
s theremoveEventListener
call fails to do anything because thescrollListener
referenced there is generated in each individual closure and will NOT match the one currently bound to the element.Video for some more evidence:
https://youtu.be/3GOi2yL88Qk
Also, sorry but I had to add
enzyme
to unit test the prop changing.