diff --git a/packages/rn-tester/js/examples/FlatList/FlatList-nested.js b/packages/rn-tester/js/examples/FlatList/FlatList-nested.js new file mode 100644 index 00000000000..f238b24c733 --- /dev/null +++ b/packages/rn-tester/js/examples/FlatList/FlatList-nested.js @@ -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 ( + + + Outer Viewable:{'\n'} + {outerItems + .map((item, i) => ({item, i})) + .filter(o => outer.viewableItems.includes(o.i)) + .map(({item, i}) => `${i} (${item})`) + .join(', ')} + + + Outer Rendered:{'\n'} + {outerItems + .map((item, i) => ({item, i})) + .filter(o => outer.renderedItems.includes(o.i)) + .map(({item, i}) => `${i} (${item})`) + .join(', ')} + + + Inner Viewable:{'\n'} + {inner.viewableItems.sort((a, b) => a - b).join(', ')} + + + Inner Rendered:{'\n'} + {inner.renderedItems.sort((a, b) => a - b).join(', ')} + + + ( + + )} + style={styles.list} + windowSize={3} + initialNumToRender={1} + onViewableItemsChanged={onViewableItemsChanged} + /> + + ); +} + +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 ( + + Header + + ); + + case 'vertical': + return ( + + + index * items.length * 3 + i)} + listKey={`${index}-col1`} + renderItem={p => ( + + )} + style={styles.childList} + onViewableItemsChanged={onViewableItemsChanged} + windowSize={1} + initialNumToRender={1} + /> + + + index * items.length * 3 + i + items.length)} + listKey={`${index}-col2`} + renderItem={p => ( + + )} + style={styles.childList} + onViewableItemsChanged={onViewableItemsChanged} + windowSize={1} + initialNumToRender={1} + /> + + + ); + + case 'horizontal': + return ( + + index * items.length * 3 + i + 2 * items.length, + )} + renderItem={p => ( + + )} + style={styles.childList} + onViewableItemsChanged={onViewableItemsChanged} + /> + + ); + + case 'filler': + return ; + } +} + +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 ( + + + {item} + + + ); +} + +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); diff --git a/packages/rn-tester/js/examples/FlatList/FlatListExampleIndex.js b/packages/rn-tester/js/examples/FlatList/FlatListExampleIndex.js index 7bcfa14d7ac..2fc61e2f55f 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatListExampleIndex.js +++ b/packages/rn-tester/js/examples/FlatList/FlatListExampleIndex.js @@ -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);