mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
VirtualizedList.RenderItem throws when using function component with hooks (#24832)
Summary:
`<VirtualizedList />` will throw an error if the `renderItem` Prop component uses hooks. Function components without hooks and class components work without issue.
Super contrived Example
```{js}
function FlatListItem({ item }) {
React.useEffect(() => console.log(item),[])
return (<Text>{item}</Text>);
}
<FlatList data={[1, 2, 3]} renderItem={FlatListItem} />
```
Example Error:
```
Invariant Violation: Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)
This error is located at:
in CellRenderer (at VirtualizedList.js:688)
in RCTScrollContentView (at ScrollView.js:976)
in RCTScrollView (at ScrollView.js:1115)
in ScrollView (at VirtualizedList.js:1081)
in VirtualizedList (at FlatList.js:632)
in FlatList (at WithoutScrollbars.js:21)
...
```
## Changelog
[General] [Added] - VirtualizedList ListItemComponent. An alternative to renderItem that accepts function components with hooks.
[General][Added] - FlatList ListItemComponent. An alternative to renderItem that accepts function components with hooks.
[General][Added] - VirtualizedList and FlatList tests and updated RNTester example
Pull Request resolved: https://github.com/facebook/react-native/pull/24832
Reviewed By: sahrens
Differential Revision: D15334020
Pulled By: cpojer
fbshipit-source-id: 882db722fd6e22f07260b08091b3456d1c66c2c8
This commit is contained in:
committed by
Facebook Github Bot
parent
9cd88251a3
commit
57a1e7c000
@@ -325,7 +325,7 @@ class NetworkOverlay extends React.Component<Props, State> {
|
||||
WebSocketInterceptor.disableInterception();
|
||||
}
|
||||
|
||||
_renderItem = ({item, index}): ?React.Element<any> => {
|
||||
_renderItem = ({item, index}): React.Element<any> => {
|
||||
const tableRowViewStyle = [
|
||||
styles.tableRow,
|
||||
index % 2 === 1 ? styles.tableRowOdd : styles.tableRowEven,
|
||||
|
||||
+94
-49
@@ -24,15 +24,20 @@ import type {
|
||||
ViewToken,
|
||||
ViewabilityConfigCallbackPair,
|
||||
} from './ViewabilityHelper';
|
||||
import type {Props as VirtualizedListProps} from './VirtualizedList';
|
||||
|
||||
export type SeparatorsObj = {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
};
|
||||
import type {
|
||||
Props as VirtualizedListProps,
|
||||
RenderItemType,
|
||||
RenderItemProps,
|
||||
} from './VirtualizedList';
|
||||
|
||||
type RequiredProps<ItemT> = {
|
||||
/**
|
||||
* For simplicity, data is just a plain array. If you want to use something else, like an
|
||||
* immutable list, use the underlying `VirtualizedList` directly.
|
||||
*/
|
||||
data: ?$ReadOnlyArray<ItemT>,
|
||||
};
|
||||
type OptionalProps<ItemT> = {
|
||||
/**
|
||||
* Takes an item from `data` and renders it into the list. Example usage:
|
||||
*
|
||||
@@ -59,18 +64,7 @@ type RequiredProps<ItemT> = {
|
||||
* `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
|
||||
* your use-case.
|
||||
*/
|
||||
renderItem: (info: {
|
||||
item: ItemT,
|
||||
index: number,
|
||||
separators: SeparatorsObj,
|
||||
}) => ?React.Node,
|
||||
/**
|
||||
* For simplicity, data is just a plain array. If you want to use something else, like an
|
||||
* immutable list, use the underlying `VirtualizedList` directly.
|
||||
*/
|
||||
data: ?$ReadOnlyArray<ItemT>,
|
||||
};
|
||||
type OptionalProps<ItemT> = {
|
||||
renderItem?: ?RenderItemType<ItemT>,
|
||||
/**
|
||||
* Rendered in between each item, but not at the top or bottom. By default, `highlighted` and
|
||||
* `leadingItem` props are provided. `renderItem` provides `separators.highlight`/`unhighlight`
|
||||
@@ -78,6 +72,33 @@ type OptionalProps<ItemT> = {
|
||||
* `separators.updateProps`.
|
||||
*/
|
||||
ItemSeparatorComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Takes an item from `data` and renders it into the list. Example usage:
|
||||
*
|
||||
* <FlatList
|
||||
* ItemSeparatorComponent={Platform.OS !== 'android' && ({highlighted}) => (
|
||||
* <View style={[style.separator, highlighted && {marginLeft: 0}]} />
|
||||
* )}
|
||||
* data={[{title: 'Title Text', key: 'item1'}]}
|
||||
* ListItemComponent={({item, separators}) => (
|
||||
* <TouchableHighlight
|
||||
* onPress={() => this._onPress(item)}
|
||||
* onShowUnderlay={separators.highlight}
|
||||
* onHideUnderlay={separators.unhighlight}>
|
||||
* <View style={{backgroundColor: 'white'}}>
|
||||
* <Text>{item.title}</Text>
|
||||
* </View>
|
||||
* </TouchableHighlight>
|
||||
* )}
|
||||
* />
|
||||
*
|
||||
* Provides additional metadata like `index` if you need it, as well as a more generic
|
||||
* `separators.updateProps` function which let's you set whatever props you want to change the
|
||||
* rendering of either the leading separator or trailing separator in case the more common
|
||||
* `highlight` and `unhighlight` (which set the `highlighted: boolean` prop) are insufficient for
|
||||
* your use-case.
|
||||
*/
|
||||
ListItemComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Rendered when the list is empty. Can be a React Component Class, a render function, or
|
||||
* a rendered element.
|
||||
@@ -598,47 +619,71 @@ class FlatList<ItemT> extends React.PureComponent<Props<ItemT>, void> {
|
||||
};
|
||||
}
|
||||
|
||||
_renderItem = (info: Object): ?React.Node => {
|
||||
const {renderItem, numColumns, columnWrapperStyle} = this.props;
|
||||
if (numColumns > 1) {
|
||||
const {item, index} = info;
|
||||
invariant(
|
||||
Array.isArray(item),
|
||||
'Expected array of items with numColumns > 1',
|
||||
);
|
||||
return (
|
||||
<View
|
||||
style={StyleSheet.compose(
|
||||
styles.row,
|
||||
columnWrapperStyle,
|
||||
)}>
|
||||
{item.map((it, kk) => {
|
||||
const element = renderItem({
|
||||
item: it,
|
||||
index: index * numColumns + kk,
|
||||
separators: info.separators,
|
||||
});
|
||||
return element != null ? (
|
||||
<React.Fragment key={kk}>{element}</React.Fragment>
|
||||
) : null;
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return renderItem(info);
|
||||
}
|
||||
_renderer = () => {
|
||||
const {
|
||||
ListItemComponent,
|
||||
renderItem,
|
||||
numColumns,
|
||||
columnWrapperStyle,
|
||||
} = this.props;
|
||||
|
||||
let virtualizedListRenderKey = ListItemComponent
|
||||
? 'ListItemComponent'
|
||||
: 'renderItem';
|
||||
|
||||
const renderer = props => {
|
||||
if (ListItemComponent) {
|
||||
return <ListItemComponent {...props} />;
|
||||
} else if (renderItem) {
|
||||
return renderItem(props);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
[virtualizedListRenderKey]: (info: RenderItemProps<ItemT>) => {
|
||||
if (numColumns > 1) {
|
||||
const {item, index} = info;
|
||||
invariant(
|
||||
Array.isArray(item),
|
||||
'Expected array of items with numColumns > 1',
|
||||
);
|
||||
return (
|
||||
<View
|
||||
style={StyleSheet.compose(
|
||||
styles.row,
|
||||
columnWrapperStyle,
|
||||
)}>
|
||||
{item.map((it, kk) => {
|
||||
const element = renderer({
|
||||
item: it,
|
||||
index: index * numColumns + kk,
|
||||
separators: info.separators,
|
||||
});
|
||||
return element != null ? (
|
||||
<React.Fragment key={kk}>{element}</React.Fragment>
|
||||
) : null;
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return renderer(info);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<VirtualizedList
|
||||
{...this.props}
|
||||
renderItem={this._renderItem}
|
||||
getItem={this._getItem}
|
||||
getItemCount={this._getItemCount}
|
||||
keyExtractor={this._keyExtractor}
|
||||
ref={this._captureRef}
|
||||
viewabilityConfigCallbackPairs={this._virtualizedListPairs}
|
||||
{...this._renderer()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ const ReactNative = require('../Renderer/shims/ReactNative');
|
||||
const RefreshControl = require('../Components/RefreshControl/RefreshControl');
|
||||
const ScrollView = require('../Components/ScrollView/ScrollView');
|
||||
const StyleSheet = require('../StyleSheet/StyleSheet');
|
||||
const UIManager = require('../ReactNative/UIManager');
|
||||
const View = require('../Components/View/View');
|
||||
const ViewabilityHelper = require('./ViewabilityHelper');
|
||||
|
||||
@@ -37,7 +36,21 @@ import type {
|
||||
|
||||
type Item = any;
|
||||
|
||||
export type renderItemType = (info: any) => ?React.Element<any>;
|
||||
export type Separators = {
|
||||
highlight: () => void,
|
||||
unhighlight: () => void,
|
||||
updateProps: (select: 'leading' | 'trailing', newProps: Object) => void,
|
||||
};
|
||||
|
||||
export type RenderItemProps<ItemT> = {
|
||||
item: ItemT,
|
||||
index: number,
|
||||
separators: Separators,
|
||||
};
|
||||
|
||||
export type RenderItemType<ItemT> = (
|
||||
info: RenderItemProps<ItemT>,
|
||||
) => React.Node;
|
||||
|
||||
type ViewabilityHelperCallbackTuple = {
|
||||
viewabilityHelper: ViewabilityHelper,
|
||||
@@ -48,9 +61,6 @@ type ViewabilityHelperCallbackTuple = {
|
||||
};
|
||||
|
||||
type RequiredProps = {
|
||||
// TODO: Conflicts with the optional `renderItem` in
|
||||
// `VirtualizedSectionList`'s props.
|
||||
renderItem: $FlowFixMe<renderItemType>,
|
||||
/**
|
||||
* The default accessor functions assume this is an Array<{key: string} | {id: string}> but you can override
|
||||
* getItem, getItemCount, and keyExtractor to handle any type of index-based data.
|
||||
@@ -66,6 +76,9 @@ type RequiredProps = {
|
||||
getItemCount: (data: any) => number,
|
||||
};
|
||||
type OptionalProps = {
|
||||
// TODO: Conflicts with the optional `renderItem` in
|
||||
// `VirtualizedSectionList`'s props.
|
||||
renderItem?: $FlowFixMe<?RenderItemType<Item>>,
|
||||
/**
|
||||
* `debug` will turn on extra logging and visual overlays to aid with debugging both usage and
|
||||
* implementation, but with a significant perf hit.
|
||||
@@ -111,6 +124,11 @@ type OptionalProps = {
|
||||
* or a render function. Defaults to using View.
|
||||
*/
|
||||
CellRendererComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Each data item is rendered using this element. Can be a React Component Class,
|
||||
* or a render function.
|
||||
*/
|
||||
ListItemComponent?: ?React.ComponentType<any>,
|
||||
/**
|
||||
* Rendered when the list is empty. Can be a React Component Class, a render function, or
|
||||
* a rendered element.
|
||||
@@ -1664,7 +1682,8 @@ class CellRenderer extends React.Component<
|
||||
onUpdateSeparators: (cellKeys: Array<?string>, props: Object) => void,
|
||||
parentProps: {
|
||||
getItemLayout?: ?Function,
|
||||
renderItem: renderItemType,
|
||||
renderItem?: ?RenderItemType<Item>,
|
||||
ListItemComponent?: ?(React.ComponentType<any> | React.Element<any>),
|
||||
},
|
||||
prevCellKey: ?string,
|
||||
},
|
||||
@@ -1725,6 +1744,36 @@ class CellRenderer extends React.Component<
|
||||
this.props.onUnmount(this.props.cellKey);
|
||||
}
|
||||
|
||||
_renderElement(renderItem, ListItemComponent, item, index) {
|
||||
if (renderItem && ListItemComponent) {
|
||||
console.warn(
|
||||
'VirtualizedList: Both ListItemComponent and renderItem props are present. ListItemComponent will take' +
|
||||
' precedence over renderItem.',
|
||||
);
|
||||
}
|
||||
|
||||
if (ListItemComponent) {
|
||||
return React.createElement(ListItemComponent, {
|
||||
item,
|
||||
index,
|
||||
separators: this._separators,
|
||||
});
|
||||
}
|
||||
|
||||
if (renderItem) {
|
||||
return renderItem({
|
||||
item,
|
||||
index,
|
||||
separators: this._separators,
|
||||
});
|
||||
}
|
||||
|
||||
invariant(
|
||||
false,
|
||||
'VirtualizedList: Either ListItemComponent or renderItem props are required but none were found.',
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
CellRendererComponent,
|
||||
@@ -1736,13 +1785,14 @@ class CellRenderer extends React.Component<
|
||||
inversionStyle,
|
||||
parentProps,
|
||||
} = this.props;
|
||||
const {renderItem, getItemLayout} = parentProps;
|
||||
invariant(renderItem, 'no renderItem!');
|
||||
const element = renderItem({
|
||||
const {renderItem, getItemLayout, ListItemComponent} = parentProps;
|
||||
const element = this._renderElement(
|
||||
renderItem,
|
||||
ListItemComponent,
|
||||
item,
|
||||
index,
|
||||
separators: this._separators,
|
||||
});
|
||||
);
|
||||
|
||||
const onLayout =
|
||||
/* $FlowFixMe(>=0.68.0 site=react_native_fb) This comment suppresses an
|
||||
* error found when Flow v0.68 was deployed. To see the error delete this
|
||||
|
||||
@@ -25,6 +25,41 @@ describe('FlatList', () => {
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
it('renders simple list (multiple columns)', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<FlatList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
renderItem={({item}) => <item value={item.key} />}
|
||||
numColumns={2}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
it('renders simple list using ListItemComponent', () => {
|
||||
function ListItemComponent({item}) {
|
||||
return <item value={item.key} />;
|
||||
}
|
||||
const component = ReactTestRenderer.create(
|
||||
<FlatList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
ListItemComponent={ListItemComponent}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
it('renders simple list using ListItemComponent (multiple columns)', () => {
|
||||
function ListItemComponent({item}) {
|
||||
return <item value={item.key} />;
|
||||
}
|
||||
const component = ReactTestRenderer.create(
|
||||
<FlatList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
ListItemComponent={ListItemComponent}
|
||||
numColumns={2}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
it('renders empty list', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<FlatList data={[]} renderItem={({item}) => <item value={item.key} />} />,
|
||||
|
||||
@@ -28,6 +28,61 @@ describe('VirtualizedList', () => {
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders simple list using ListItemComponent', () => {
|
||||
function ListItemComponent({item}) {
|
||||
return <item value={item.key} />;
|
||||
}
|
||||
const component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
ListItemComponent={ListItemComponent}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('warns if both renderItem or ListItemComponent are specified. Uses ListItemComponent', () => {
|
||||
jest.spyOn(global.console, 'warn');
|
||||
function ListItemComponent({item}) {
|
||||
return <item value={item.key} testID={`${item.key}-ListItemComponent`} />;
|
||||
}
|
||||
const component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
data={[{key: 'i1'}]}
|
||||
ListItemComponent={ListItemComponent}
|
||||
renderItem={({item}) => (
|
||||
<item value={item.key} testID={`${item.key}-renderItem`} />
|
||||
)}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(console.warn.mock.calls).toEqual([
|
||||
[
|
||||
'VirtualizedList: Both ListItemComponent and renderItem props are present. ListItemComponent will take precedence over renderItem.',
|
||||
],
|
||||
]);
|
||||
expect(component).toMatchSnapshot();
|
||||
console.warn.mockRestore();
|
||||
});
|
||||
|
||||
it('throws if no renderItem or ListItemComponent', () => {
|
||||
const componentFactory = () =>
|
||||
ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
data={[{key: 'i1'}, {key: 'i2'}, {key: 'i3'}]}
|
||||
getItem={(data, index) => data[index]}
|
||||
getItemCount={data => data.length}
|
||||
/>,
|
||||
);
|
||||
expect(componentFactory).toThrow(
|
||||
'VirtualizedList: Either ListItemComponent or renderItem props are required but none were found.',
|
||||
);
|
||||
});
|
||||
|
||||
it('renders empty list', () => {
|
||||
const component = ReactTestRenderer.create(
|
||||
<VirtualizedList
|
||||
|
||||
@@ -185,6 +185,84 @@ exports[`FlatList renders null list 1`] = `
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`FlatList renders simple list (multiple columns) 1`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "i1",
|
||||
},
|
||||
Object {
|
||||
"key": "i2",
|
||||
},
|
||||
Object {
|
||||
"key": "i3",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={2}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
removeClippedSubviews={false}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
viewabilityConfigCallbackPairs={Array []}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="i1"
|
||||
/>
|
||||
<item
|
||||
value="i2"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="i3"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`FlatList renders simple list 1`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
@@ -251,3 +329,148 @@ exports[`FlatList renders simple list 1`] = `
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`FlatList renders simple list using ListItemComponent (multiple columns) 1`] = `
|
||||
<RCTScrollView
|
||||
ListItemComponent={[Function]}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "i1",
|
||||
},
|
||||
Object {
|
||||
"key": "i2",
|
||||
},
|
||||
Object {
|
||||
"key": "i3",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={2}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
removeClippedSubviews={false}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
viewabilityConfigCallbackPairs={Array []}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="i1"
|
||||
/>
|
||||
<item
|
||||
value="i2"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<item
|
||||
value="i3"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`FlatList renders simple list using ListItemComponent 1`] = `
|
||||
<RCTScrollView
|
||||
ListItemComponent={[Function]}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "i1",
|
||||
},
|
||||
Object {
|
||||
"key": "i2",
|
||||
},
|
||||
Object {
|
||||
"key": "i3",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
numColumns={1}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
removeClippedSubviews={false}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
viewabilityConfigCallbackPairs={Array []}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i1"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i2"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i3"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
@@ -811,6 +811,70 @@ exports[`VirtualizedList renders simple list 1`] = `
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`VirtualizedList renders simple list using ListItemComponent 1`] = `
|
||||
<RCTScrollView
|
||||
ListItemComponent={[Function]}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "i1",
|
||||
},
|
||||
Object {
|
||||
"key": "i2",
|
||||
},
|
||||
Object {
|
||||
"key": "i3",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i1"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i2"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
value="i3"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`VirtualizedList test getItem functionality where data is not an Array 1`] = `
|
||||
<RCTScrollView
|
||||
data={
|
||||
@@ -852,3 +916,47 @@ exports[`VirtualizedList test getItem functionality where data is not an Array 1
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
exports[`VirtualizedList warns if both renderItem or ListItemComponent are specified. Uses ListItemComponent 1`] = `
|
||||
<RCTScrollView
|
||||
ListItemComponent={[Function]}
|
||||
data={
|
||||
Array [
|
||||
Object {
|
||||
"key": "i1",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableVirtualization={false}
|
||||
getItem={[Function]}
|
||||
getItemCount={[Function]}
|
||||
horizontal={false}
|
||||
initialNumToRender={10}
|
||||
keyExtractor={[Function]}
|
||||
maxToRenderPerBatch={10}
|
||||
onContentSizeChange={[Function]}
|
||||
onEndReachedThreshold={2}
|
||||
onLayout={[Function]}
|
||||
onMomentumScrollEnd={[Function]}
|
||||
onScroll={[Function]}
|
||||
onScrollBeginDrag={[Function]}
|
||||
onScrollEndDrag={[Function]}
|
||||
renderItem={[Function]}
|
||||
scrollEventThrottle={50}
|
||||
stickyHeaderIndices={Array []}
|
||||
updateCellsBatchingPeriod={50}
|
||||
windowSize={21}
|
||||
>
|
||||
<View>
|
||||
<View
|
||||
onLayout={[Function]}
|
||||
style={null}
|
||||
>
|
||||
<item
|
||||
testID="i1-ListItemComponent"
|
||||
value="i1"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</RCTScrollView>
|
||||
`;
|
||||
|
||||
@@ -51,6 +51,7 @@ type State = {|
|
||||
logViewable: boolean,
|
||||
virtualized: boolean,
|
||||
empty: boolean,
|
||||
useFlatListItemComponent: boolean,
|
||||
|};
|
||||
|
||||
class FlatListExample extends React.PureComponent<Props, State> {
|
||||
@@ -64,6 +65,7 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
logViewable: false,
|
||||
virtualized: true,
|
||||
empty: false,
|
||||
useFlatListItemComponent: false,
|
||||
};
|
||||
|
||||
_onChangeFilterText = filterText => {
|
||||
@@ -95,6 +97,7 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
const filter = item =>
|
||||
filterRegex.test(item.text) || filterRegex.test(item.title);
|
||||
const filteredData = this.state.data.filter(filter);
|
||||
const flatListItemRendererProps = this._renderItemComponent();
|
||||
return (
|
||||
<RNTesterPage noSpacer={true} noScroll={true}>
|
||||
<View style={styles.container}>
|
||||
@@ -118,6 +121,7 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
{renderSmallSwitchOption(this, 'inverted')}
|
||||
{renderSmallSwitchOption(this, 'empty')}
|
||||
{renderSmallSwitchOption(this, 'debug')}
|
||||
{renderSmallSwitchOption(this, 'useFlatListItemComponent')}
|
||||
<Spindicator value={this._scrollPos} />
|
||||
</View>
|
||||
</View>
|
||||
@@ -150,9 +154,9 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
onViewableItemsChanged={this._onViewableItemsChanged}
|
||||
ref={this._captureRef}
|
||||
refreshing={false}
|
||||
renderItem={this._renderItemComponent}
|
||||
contentContainerStyle={styles.list}
|
||||
viewabilityConfig={VIEWABILITY_CONFIG}
|
||||
{...flatListItemRendererProps}
|
||||
/>
|
||||
</View>
|
||||
</RNTesterPage>
|
||||
@@ -173,17 +177,26 @@ class FlatListExample extends React.PureComponent<Props, State> {
|
||||
}));
|
||||
};
|
||||
_onRefresh = () => Alert.alert('onRefresh: nothing to refresh :P');
|
||||
_renderItemComponent = ({item, separators}) => {
|
||||
return (
|
||||
<ItemComponent
|
||||
item={item}
|
||||
horizontal={this.state.horizontal}
|
||||
fixedHeight={this.state.fixedHeight}
|
||||
onPress={this._pressItem}
|
||||
onShowUnderlay={separators.highlight}
|
||||
onHideUnderlay={separators.unhighlight}
|
||||
/>
|
||||
);
|
||||
_renderItemComponent = () => {
|
||||
const flatListPropKey = this.state.useFlatListItemComponent
|
||||
? 'ListItemComponent'
|
||||
: 'renderItem';
|
||||
|
||||
return {
|
||||
renderItem: undefined,
|
||||
[flatListPropKey]: ({item, separators}) => {
|
||||
return (
|
||||
<ItemComponent
|
||||
item={item}
|
||||
horizontal={this.state.horizontal}
|
||||
fixedHeight={this.state.fixedHeight}
|
||||
onPress={this._pressItem}
|
||||
onShowUnderlay={separators.highlight}
|
||||
onHideUnderlay={separators.unhighlight}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
};
|
||||
// This is called when items change viewability by scrolling into or out of
|
||||
// the viewable area.
|
||||
|
||||
Reference in New Issue
Block a user