mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Skip cloning Fragments in ListEmptyComponent to avoid onLayout warning (#50833)
Summary: Fixes https://github.com/facebook/react-native/issues/50817 Using a fragment is very common when rendering elements. We are cloning and adding `onLayout` always to the ListEmptyComponent element, but this would seem to work only when `View` is used for this as a wrapper in this prop. To prevent this unnecessary warning, I think we can easily check whether it is a fragment or not before cloning and adding the extra props – this adds backwards compatibility for those that don't need to use `onLayout`. ## Changelog: [GENERAL] [FIXED] - Skip cloning Fragments in ListEmptyComponent to avoid onLayout warning Pull Request resolved: https://github.com/facebook/react-native/pull/50833 Test Plan: Use the code snippet from the linked issue to verify that the warning is not thrown anymore when using a Fragment. Reviewed By: javache Differential Revision: D73421503 Pulled By: rshest fbshipit-source-id: 0da4a38130601943e4704589ac275eba39767191
This commit is contained in:
committed by
Facebook GitHub Bot
parent
04348e9db6
commit
2b0189b964
@@ -891,6 +891,30 @@ class VirtualizedList extends StateSafePureComponent<
|
||||
return key;
|
||||
}
|
||||
|
||||
_renderEmptyComponent(
|
||||
element: ExactReactElement_DEPRECATED<any>,
|
||||
inversionStyle: StyleProp<ViewStyle>,
|
||||
): React.Node {
|
||||
// $FlowFixMe[prop-missing] React.Element internal inspection
|
||||
const isFragment = element.type === React.Fragment;
|
||||
|
||||
if (isFragment) {
|
||||
return element;
|
||||
}
|
||||
|
||||
return React.cloneElement(element, {
|
||||
onLayout: (event: LayoutChangeEvent) => {
|
||||
this._onLayoutEmpty(event);
|
||||
// $FlowFixMe[prop-missing] React.Element internal inspection
|
||||
if (element.props.onLayout) {
|
||||
element.props.onLayout(event);
|
||||
}
|
||||
},
|
||||
// $FlowFixMe[prop-missing] React.Element internal inspection
|
||||
style: StyleSheet.compose(inversionStyle, element.props.style),
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.Node {
|
||||
this._checkProps(this.props);
|
||||
const {ListEmptyComponent, ListFooterComponent, ListHeaderComponent} =
|
||||
@@ -956,17 +980,7 @@ class VirtualizedList extends StateSafePureComponent<
|
||||
<VirtualizedListCellContextProvider
|
||||
cellKey={this._getCellKey() + '-empty'}
|
||||
key="$empty">
|
||||
{React.cloneElement(element, {
|
||||
onLayout: (event: LayoutChangeEvent) => {
|
||||
this._onLayoutEmpty(event);
|
||||
// $FlowFixMe[prop-missing] React.Element internal inspection
|
||||
if (element.props.onLayout) {
|
||||
element.props.onLayout(event);
|
||||
}
|
||||
},
|
||||
// $FlowFixMe[prop-missing] React.Element internal inspection
|
||||
style: StyleSheet.compose(inversionStyle, element.props.style),
|
||||
})}
|
||||
{this._renderEmptyComponent(element, inversionStyle)}
|
||||
</VirtualizedListCellContextProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -337,6 +337,95 @@ describe('VirtualizedList', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('empty component returns the original element if it is a React.Fragment', () => {
|
||||
const listRef = React.createRef();
|
||||
const fragment = (
|
||||
<React.Fragment>
|
||||
<div>Test</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
create(
|
||||
<VirtualizedList
|
||||
ref={listRef}
|
||||
data={[]}
|
||||
ListEmptyComponent={fragment}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const result = listRef.current._renderEmptyComponent(fragment, null);
|
||||
expect(result).toBe(fragment);
|
||||
});
|
||||
|
||||
it('empty component clones the element and adds onLayout and style props if not a Fragment', () => {
|
||||
const listRef = React.createRef();
|
||||
const element = <div>Test</div>;
|
||||
const inversionStyle = {transform: [{scaleY: -1}]};
|
||||
|
||||
act(() => {
|
||||
create(
|
||||
<VirtualizedList
|
||||
ref={listRef}
|
||||
data={[]}
|
||||
ListEmptyComponent={element}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const result = listRef.current._renderEmptyComponent(
|
||||
element,
|
||||
inversionStyle,
|
||||
);
|
||||
|
||||
// The result should be a cloned element with additional props
|
||||
expect(result).not.toBe(element);
|
||||
expect(result.props).toEqual(
|
||||
expect.objectContaining({
|
||||
onLayout: expect.any(Function),
|
||||
style: inversionStyle,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('empty component preserves original onLayout handler if present', () => {
|
||||
const listRef = React.createRef();
|
||||
const originalOnLayout = jest.fn();
|
||||
const element = <div onLayout={originalOnLayout}>Test</div>;
|
||||
const inversionStyle = {transform: [{scaleY: -1}]};
|
||||
|
||||
act(() => {
|
||||
create(
|
||||
<VirtualizedList
|
||||
ref={listRef}
|
||||
data={[]}
|
||||
ListEmptyComponent={element}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
|
||||
const result = listRef.current._renderEmptyComponent(
|
||||
element,
|
||||
inversionStyle,
|
||||
);
|
||||
|
||||
// Call the onLayout handler
|
||||
result.props.onLayout({nativeEvent: {layout: {width: 100, height: 100}}});
|
||||
|
||||
// Both the original and the new onLayout should be called
|
||||
expect(originalOnLayout).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns the viewableItems correctly in the onViewableItemsChanged callback after changing the data', async () => {
|
||||
const ITEM_HEIGHT = 800;
|
||||
let data = [{key: 'i1'}, {key: 'i2'}, {key: 'i3'}];
|
||||
|
||||
Reference in New Issue
Block a user