Add Nested FlatList RNTester Example

Summary:
This adds an example FlatList with nested children of both orientations. Debug text is shown for what is detected to be visible, and what is currently being rendered.

Changelog:
[Internal][Added] - Add Nested FlatList RNTester Example

Reviewed By: rshest

Differential Revision: D39466679

fbshipit-source-id: 5d6ce4adb9a862ca96b1a7b268b70101207d2980
This commit is contained in:
Nick Gerleman
2022-09-14 04:09:52 -07:00
committed by Facebook GitHub Bot
parent aba82a503d
commit be620298cd
2 changed files with 331 additions and 0 deletions
@@ -0,0 +1,329 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
'use strict';
import * as React from 'react';
import {useCallback, useEffect, useReducer} from 'react';
import {FlatList, StyleSheet, Text, View} from 'react-native';
import RNTesterPage from '../../components/RNTesterPage';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
type OuterItem = 'head' | 'vertical' | 'horizontal' | 'filler';
const outerItems: OuterItem[] = [
'head',
'vertical',
'filler',
'horizontal',
'filler',
'vertical',
];
const items = [1, 2, 3, 4, 5];
type ItemsState = {
renderedItems: number[],
viewableItems: number[],
};
const initialItemsState: ItemsState = {
renderedItems: [],
viewableItems: [],
};
type ItemsAction = {
type: 'add-rendered' | 'add-viewable' | 'remove-rendered' | 'remove-viewable',
item: number,
};
function reducer(state: ItemsState, action: ItemsAction): ItemsState {
if (action.type === 'add-rendered') {
if (state.renderedItems.includes(action.item)) {
return state;
} else {
return {...state, renderedItems: [...state.renderedItems, action.item]};
}
} else if (action.type === 'add-viewable') {
if (state.viewableItems.includes(action.item)) {
return state;
} else {
return {...state, viewableItems: [...state.viewableItems, action.item]};
}
} else if (action.type === 'remove-rendered') {
return {
...state,
renderedItems: state.renderedItems.filter(i => i !== action.item),
};
} else if (action.type === 'remove-viewable') {
return {
...state,
viewableItems: state.viewableItems.filter(i => i !== action.item),
};
}
return state;
}
function NestedListExample(): React.Node {
const [outer, dispatchOuter] = useReducer(reducer, initialItemsState);
const [inner, dispatchInner] = useReducer(reducer, initialItemsState);
const onViewableItemsChanged = useCallback(
({changed}) => {
for (const token of changed) {
dispatchOuter({
type: token.isViewable ? 'add-viewable' : 'remove-viewable',
item: token.index ?? -1,
});
}
},
[dispatchOuter],
);
return (
<RNTesterPage noSpacer={true} noScroll={true}>
<Text style={styles.debugText}>
<Text style={styles.debugTextHeader}>Outer Viewable:{'\n'}</Text>
{outerItems
.map((item, i) => ({item, i}))
.filter(o => outer.viewableItems.includes(o.i))
.map(({item, i}) => `${i} (${item})`)
.join(', ')}
</Text>
<Text style={styles.debugText}>
<Text style={styles.debugTextHeader}>Outer Rendered:{'\n'}</Text>
{outerItems
.map((item, i) => ({item, i}))
.filter(o => outer.renderedItems.includes(o.i))
.map(({item, i}) => `${i} (${item})`)
.join(', ')}
</Text>
<Text style={styles.debugText}>
<Text style={styles.debugTextHeader}>Inner Viewable:{'\n'}</Text>
{inner.viewableItems.sort((a, b) => a - b).join(', ')}
</Text>
<Text style={styles.debugText}>
<Text style={styles.debugTextHeader}>Inner Rendered:{'\n'}</Text>
{inner.renderedItems.sort((a, b) => a - b).join(', ')}
</Text>
<FlatList
data={outerItems}
renderItem={({index, item}) => (
<OuterItemRenderer
index={index}
item={item}
dispatchOuter={dispatchOuter}
dispatchInner={dispatchInner}
/>
)}
style={styles.list}
windowSize={3}
initialNumToRender={1}
onViewableItemsChanged={onViewableItemsChanged}
/>
</RNTesterPage>
);
}
function OuterItemRenderer({
index,
item,
dispatchOuter,
dispatchInner,
}: {
index: number,
item: OuterItem,
dispatchOuter: ItemsAction => void,
dispatchInner: ItemsAction => void,
...
}) {
useEffect(() => {
dispatchOuter({
type: 'add-rendered',
item: index,
});
return () => {
dispatchOuter({
type: 'remove-rendered',
item: index,
});
};
}, [dispatchOuter, index]);
const onViewableItemsChanged = useCallback(
({changed}) => {
for (const token of changed) {
dispatchInner({
type: token.isViewable ? 'add-viewable' : 'remove-viewable',
item: token.item,
});
}
},
[dispatchInner],
);
switch (item) {
case 'head':
return (
<View style={styles.header}>
<Text>Header</Text>
</View>
);
case 'vertical':
return (
<View style={styles.body}>
<View style={styles.col}>
<FlatList
data={items.map(i => index * items.length * 3 + i)}
listKey={`${index}-col1`}
renderItem={p => (
<InnerItemRenderer
item={p.item}
dispatchInner={dispatchInner}
/>
)}
style={styles.childList}
onViewableItemsChanged={onViewableItemsChanged}
windowSize={1}
initialNumToRender={1}
/>
</View>
<View style={styles.col}>
<FlatList
data={items.map(i => index * items.length * 3 + i + items.length)}
listKey={`${index}-col2`}
renderItem={p => (
<InnerItemRenderer
item={p.item}
dispatchInner={dispatchInner}
/>
)}
style={styles.childList}
onViewableItemsChanged={onViewableItemsChanged}
windowSize={1}
initialNumToRender={1}
/>
</View>
</View>
);
case 'horizontal':
return (
<View style={styles.row}>
<FlatList
horizontal={true}
data={items.map(
i => index * items.length * 3 + i + 2 * items.length,
)}
renderItem={p => (
<InnerItemRenderer item={p.item} dispatchInner={dispatchInner} />
)}
style={styles.childList}
onViewableItemsChanged={onViewableItemsChanged}
/>
</View>
);
case 'filler':
return <View style={styles.filler} />;
}
}
function InnerItemRenderer({
item,
dispatchInner,
}: {
item: number,
dispatchInner: ItemsAction => void,
...
}) {
useEffect(() => {
dispatchInner({
type: 'add-rendered',
item: item,
});
return () => {
dispatchInner({
type: 'remove-rendered',
item: item,
});
};
}, [dispatchInner, item]);
return (
<View style={styles.cell}>
<View style={styles.item}>
<Text>{item}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
debugText: {
fontSize: 10,
},
debugTextHeader: {
fontWeight: 'bold',
},
list: {
borderWidth: 1,
borderColor: 'black',
},
body: {
flexDirection: 'row',
justifyContent: 'space-around',
},
col: {
flex: 1,
padding: 10,
},
row: {
flex: 1,
},
filler: {
height: 72,
backgroundColor: 'lightblue',
},
childList: {
backgroundColor: 'lightgreen',
},
header: {
height: 40,
backgroundColor: 'lightcoral',
alignItems: 'center',
justifyContent: 'center',
},
cell: {
padding: 10,
},
item: {
alignItems: 'center',
justifyContent: 'center',
borderWidth: 1,
borderColor: 'black',
backgroundColor: 'oldlace',
height: 72,
minWidth: 144,
},
});
export default ({
title: 'Nested',
description: 'Nested FlatLists of same and opposite orientation',
name: 'nested',
render: NestedListExample,
}: RNTesterModuleExample);
@@ -17,6 +17,7 @@ import onViewableItemsChangedExample from './FlatList-onViewableItemsChanged';
import WithSeparatorsExample from './FlatList-withSeparators';
import MultiColumnExample from './FlatList-multiColumn';
import StickyHeadersExample from './FlatList-stickyHeaders';
import NestedExample from './FlatList-nested';
export default ({
framework: 'React',
@@ -34,5 +35,6 @@ export default ({
WithSeparatorsExample,
MultiColumnExample,
StickyHeadersExample,
NestedExample,
],
}: RNTesterModule);