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