Fix virtualized cell keys for list headers and footers

Summary:
The change enabling virtualization in nested lists contained a hidden assumption that nested lists would only appear within the *cells* of a parent list.

If a list header or footer component contains a `VirtualizedList`, that child list won't be wrapped in a `CellRenderer` component and therefore won't have access to `virtualizedCellRenderer` through its context. This causes an error when the child list tries to access the `cellKey` property on an undefined object.

This change wraps the header/footer views in a `VirtualizedCellWrapper` component which supplies that context properly.

Reviewed By: sahrens

Differential Revision: D6603342

fbshipit-source-id: 4d2d82f04947048a16ec9968121d8ecc8c95655a
This commit is contained in:
Logan Daniels 2017-12-19 13:43:08 -08:00 committed by Facebook Github Bot
parent f1055bcac8
commit a010a0cebd
1 changed files with 50 additions and 24 deletions

View File

@ -432,7 +432,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
};
static contextTypes = {
virtualizedListCellRenderer: PropTypes.shape({
virtualizedCell: PropTypes.shape({
cellKey: PropTypes.string,
}),
virtualizedList: PropTypes.shape({
@ -466,6 +466,13 @@ class VirtualizedList extends React.PureComponent<Props, State> {
};
}
_getCellKey(): string {
return (
(this.context.virtualizedCell && this.context.virtualizedCell.cellKey) ||
'rootList'
);
}
_getScrollMetrics = () => {
return this._scrollMetrics;
};
@ -559,10 +566,8 @@ class VirtualizedList extends React.PureComponent<Props, State> {
if (this._isNestedWithSameOrientation()) {
const storedState = this.context.virtualizedList.registerAsNestedChild({
cellKey: this.context.virtualizedListCellRenderer.cellKey,
key:
this.props.listKey ||
this.context.virtualizedListCellRenderer.cellKey,
cellKey: this._getCellKey(),
key: this.props.listKey || this._getCellKey(),
ref: this,
});
if (storedState) {
@ -575,8 +580,6 @@ class VirtualizedList extends React.PureComponent<Props, State> {
this.state = initialState;
}
componentWillMount() {}
componentDidMount() {
if (this.props.initialScrollIndex) {
this._initialScrollIndexTimeout = setTimeout(
@ -593,9 +596,7 @@ class VirtualizedList extends React.PureComponent<Props, State> {
componentWillUnmount() {
if (this._isNestedWithSameOrientation()) {
this.context.virtualizedList.unregisterAsNestedChild({
key:
this.props.listKey ||
this.context.virtualizedListCellRenderer.cellKey,
key: this.props.listKey || this._getCellKey(),
state: {
first: this.state.first,
last: this.state.last,
@ -743,12 +744,13 @@ class VirtualizedList extends React.PureComponent<Props, State> {
<ListHeaderComponent />
);
cells.push(
<View
key="$header"
onLayout={this._onLayoutHeader}
style={inversionStyle}>
{element}
</View>,
<VirtualizedCellWrapper
cellKey={this._getCellKey() + '-header'}
key="$header">
<View onLayout={this._onLayoutHeader} style={inversionStyle}>
{element}
</View>
</VirtualizedCellWrapper>,
);
}
const itemCount = this.props.getItemCount(data);
@ -867,12 +869,13 @@ class VirtualizedList extends React.PureComponent<Props, State> {
<ListFooterComponent />
);
cells.push(
<View
key="$footer"
onLayout={this._onLayoutFooter}
style={inversionStyle}>
{element}
</View>,
<VirtualizedCellWrapper
cellKey={this._getCellKey() + '-footer'}
key="$footer">
<View onLayout={this._onLayoutFooter} style={inversionStyle}>
{element}
</View>
</VirtualizedCellWrapper>,
);
}
const scrollProps = {
@ -1522,14 +1525,14 @@ class CellRenderer extends React.Component<
};
static childContextTypes = {
virtualizedListCellRenderer: PropTypes.shape({
virtualizedCell: PropTypes.shape({
cellKey: PropTypes.string,
}),
};
getChildContext() {
return {
virtualizedListCellRenderer: {
virtualizedCell: {
cellKey: this.props.cellKey,
},
};
@ -1621,6 +1624,29 @@ class CellRenderer extends React.Component<
}
}
class VirtualizedCellWrapper extends React.Component<{
cellKey: string,
children: React.Node,
}> {
static childContextTypes = {
virtualizedCell: PropTypes.shape({
cellKey: PropTypes.string,
}),
};
getChildContext() {
return {
virtualizedCell: {
cellKey: this.props.cellKey,
},
};
}
render() {
return this.props.children;
}
}
const styles = StyleSheet.create({
verticallyInverted: {
transform: [{scaleY: -1}],