-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix List with CellMeasurer on first paint #959
Fix List with CellMeasurer on first paint #959
Conversation
This is a fantastic PR write-up. Thank you so much for taking the time to put all of these details together! 🙇 I think there's a simpler solution than the one you've proposed with this PR: Just pass diff --git a/source/List/List.js b/source/List/List.js
index 0822666..c2dbdfe 100644
--- a/source/List/List.js
+++ b/source/List/List.js
@@ -209,6 +209,7 @@ export default class List extends React.PureComponent<Props> {
}
_cellRenderer = ({
+ parent,
rowIndex,
style,
isScrolling,
@@ -235,7 +236,7 @@ export default class List extends React.PureComponent<Props> {
isScrolling,
isVisible,
key,
- parent: this,
+ parent,
});
}; What do you think? This is actually what |
Based on my own local testing (using the steps you outlined above) this solution fixes the problem. Please let me know if I've misunderstood or overlooked something! And thank you so much, again! |
Sure thing @bvaughn ! I actually have thought about this option but assumed it was the least desirable 🙄 If and when there's gonna be a Plus, I just realized that there are other components that decorate Would love to hear your thoughts on this since I'm the outsider here and might not know all the inner workings of all the components. |
I appreciate your sharing these follow-up thoughts with me. 😄 Here's my thinking:
There are two others:
|
PS. Interested in contributing a PR to fix |
Makes sense 👍 I just wasn't sure about it and so I tried thinking of a broader solution.
I think invalidateCellSizeAfterRender({columnIndex = 0, rowIndex = 0} = {}) {
this._deferredInvalidateColumnIndex =
typeof this._deferredInvalidateColumnIndex === 'number'
? Math.min(this._deferredInvalidateColumnIndex, columnIndex)
: columnIndex;
this._deferredInvalidateRowIndex =
typeof this._deferredInvalidateRowIndex === 'number'
? Math.min(this._deferredInvalidateRowIndex, rowIndex)
: rowIndex;
} And later in Or am I missing something? |
Ah, good observation. I haven't looked at |
fix #866
The Problem
Going back to the original issue - the problem was that
List
withCellMeasurer
rendered the children incorrectly but after the first scroll - everything went back to normal.After the initial findings coming from @steinso, which turned out to be true I wanted to dig a bit deeper into why the
Grid
ref inList
wasn't initialized when callinginvalidateCellSizeAfterRender
on theList
but only on the first render (after mount).I realized that the
List._setRef
is being called afterList.invalidateCellSizeAfterRender
which is called (indirectly) inCellMeasurer.componentDidMount
.So I looked at reacts source code and figured out that this is by design, component lifecycle hooks are being triggered before attaching refs.
(notice
commitLifeCycles
react-dom.development.js:11505)(notice
commitAttachRef
react-dom.development.js:11579)Plus,
CellMeasurer
is returned throughrowRenderer
which in turn, is returned tocellRenderer
and is then rendered byGrid
, so the component hierarchy is something like this:This means that
Grid.componentDidMount
will only be called when all ofcomponentDidMount
of all of his children have been executed, and since attaching refs only happens after life cycle hooks, it means that react will attach theGrid
ref to theList
only after all theCellMeasurer
children have been mounted.To conclude, this is the call order (of the calls we care about):
Grid.props.cellRenderer
(for each cell)List.props.rowRenderer
(for each cell)CellMeasurer.componentDidMount
(for eachCellMeasurer
)List.invalidateCellSizeAfterRender
(for eachCellMeasurer
)Grid.ComponentDidMount
List._setRef
(for getting theGrid
s ref)The solutions
I thought of several solutions to solve this but couldn't decide which is better so I did all of them, each in its own commit.
Simple solution
This one is the easiest to understand, since the first call in the call chain is
Grid.props.cellRenderer
which callsList._cellRenderer
, we can use that.Grid
invokes tocellRenderer
with aparent
parameter, we can use that inList._cellRenderer
to do this simple check:This way we're setting the "ref" for the
Grid
a lot sooner, thus ensuringGrid.invalidateCellSizeAfterRender
will be called, even duringCellMeasurer.componentDidMount
.Deferred solution
Instead of trying to set the ref every time on
List._cellRenderer
, we can do everything regularly, except that if we find thatthis.Grid
is not initialized we add a desired callback (basically callingGrid.invalidateCellSizeAfterRender
) to a deferred callback list that will be invoked once we actually setthis.Grid
.The only problem is that now the
Grid.componentDidMount
is being called before all of our deferred callbacks (they're invoked inList._setRef
) which means it'll miss the call toGrid._handleInvalidatedGridSize
inGrid.componentDidMount
, so we also add a call toList.forceUpdateGrid
inList._setRef
, that will cause a render but also invokeGrid._handleInvalidatedGridSize
inGrid.componentDidUpdate
.Premount solution
This solution tries another approach, setting the
Grid
s ref really early, even before the life cycle hooks. It does that by adding a new prop toGrid
that's calledpreMountRef
and it's invoked inGrid.componentWillMount
withthis
as theGrid
ref. That way theList
will have the ref before the firstCellMeasurer
is being rendered - which is what we want.To me it feels like a dirty trick, but maybe that's just me 🤷♂️
Testing each solution
I couldn't easily reproduce this issue with the local examples, so what I did was to edit
source/demo/Application.js
and replace everything there with:(This is the code from the plunker in the original issue)
Update: I managed to figure out why I couldn't reproduce the issue with the original
CellMeasurer
examples. That's because the examples useAutoSizer
which transfer the width to each example, and onAutoSizer.componentDidMount
it callssetState
with the new dimensions, this triggers another render cycle so now all the refs exist so everything appears to be working.So a side note here - I'd make each example page as isolated as possible to avoid example specific quirks like these.
I'll update the tests/examples/docs once I'll know which solution to use 😉
P.S.
I did
nvm use
once I saw there's.nvmrc
, butyarn install
resulted in a segmentation fault when runningnpm run lint
😳So I deleted
node_modules
and rannvm use 8
and later everything was fine.I don't know if it's my machine or not but it's worth upgrading
.nvmrc
to8.x.x
.