Created
August 1, 2025 16:37
-
-
Save DanielSRS/d91a44d40093d5c5cf1b0051ef4ac120 to your computer and use it in GitHub Desktop.
Experimenting rendering large element trees
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| FlatList, | |
| Text, | |
| View, | |
| type LayoutRectangle, | |
| type ListRenderItemInfo, | |
| } from 'react-native'; | |
| /** | |
| * Importing an arbitrary JSON file to test rendering. | |
| */ | |
| import jsonData from './startwitharray.json'; | |
| import { LegendList, type LegendListRenderItemProps } from '@legendapp/list'; | |
| import { useMemo, useState } from 'react'; | |
| const flatJson = flattenJson(jsonData); | |
| /** | |
| * Duplicate the data just the test rendering of multiple trees. | |
| */ | |
| const jsonList = { | |
| data: [0, 1], | |
| elements: [flatJson, flatJson], | |
| }; | |
| const PADDING = 16; | |
| /** | |
| * JSON list Viewer component. | |
| */ | |
| export function JsonViewer() { | |
| const [layout, setLayout] = useState<LayoutRectangle | undefined>(); | |
| const renderJsonTree = useMemo(() => RenderJsonTree(layout), [layout]); | |
| return ( | |
| <FlatList | |
| data={jsonList.data} | |
| contentContainerStyle={{ | |
| paddingHorizontal: PADDING, | |
| rowGap: PADDING, | |
| }} | |
| onLayout={({ nativeEvent }) => { | |
| setLayout(nativeEvent.layout); | |
| }} | |
| keyExtractor={item => item.toString()} | |
| renderItem={renderJsonTree} | |
| /> | |
| ); | |
| } | |
| const RenderJsonTree = | |
| (layout?: LayoutRectangle) => (i: ListRenderItemInfo<number>) => { | |
| const e = jsonList.elements[i.item]; | |
| /** | |
| * This is thinking about legend state, where the element is deleted | |
| * faster than the list rerenders. | |
| * | |
| * in this example 'e' always exists. | |
| */ | |
| if (!e) { | |
| return null; | |
| } | |
| // wait for layout to be set before rendering the list | |
| if (!layout) { | |
| return null; | |
| } | |
| return ( | |
| <LegendList | |
| data={e.keys} | |
| estimatedItemSize={21} | |
| keyExtractor={item => item} | |
| style={{ | |
| height: layout.height - PADDING * 2, | |
| width: layout.width - PADDING * 2, | |
| }} | |
| contentContainerStyle={treeContainerStyle} | |
| renderItem={RenderJsonTreeItem} | |
| recycleItems | |
| /> | |
| ); | |
| }; | |
| const treeContainerStyle = { | |
| backgroundColor: 'rgba(50, 0, 255, 0.1)', | |
| } as const; | |
| function RenderJsonTreeItem(item: LegendListRenderItemProps<string>) { | |
| const element = flatJson.elements[item.item]; | |
| if (!element) { | |
| return null; | |
| } | |
| return ( | |
| <View style={jsonTreeItemStyle}> | |
| <Text | |
| style={{ | |
| paddingLeft: element.indentation * 16, | |
| }}> | |
| {element.key}: | |
| </Text> | |
| <Text> | |
| {typeof element.value === 'object' | |
| ? element.value === null | |
| ? 'null' | |
| : '{...}' | |
| : String(element.value)} | |
| </Text> | |
| </View> | |
| ); | |
| } | |
| const jsonTreeItemStyle = { | |
| flexDirection: 'row', | |
| paddingVertical: 2, | |
| gap: 4, | |
| // Fixing height, because legend list showed som warnings | |
| // maybe because the amount of items | |
| height: 21, | |
| maxHeight: 21, | |
| } as const; | |
| /** | |
| * Flattened JSON result type. | |
| * It stores the elements in a record rather than a list. | |
| * this is thinking about the legend state recomended way of storing | |
| * data, where the elements are stored in a record with a unique key. | |
| */ | |
| type FlattenedJsonResult = { | |
| keys: string[]; | |
| elements: Record< | |
| string, | |
| { | |
| path: string; | |
| key: string; | |
| value: any; | |
| indentation: number; | |
| } | |
| >; | |
| }; | |
| /** | |
| * Super simple JSON flattener. | |
| */ | |
| function flattenJson(json: any): FlattenedJsonResult { | |
| const result: FlattenedJsonResult = { | |
| keys: [], | |
| elements: {}, | |
| }; | |
| function recurse(obj: any, currentPath = '', key = '', indentation = 0) { | |
| if (typeof obj !== 'object' || obj === null) { | |
| const id = `${result.keys.length}`; | |
| result.keys.push(id); | |
| result.elements[id] = { | |
| path: currentPath, | |
| key, | |
| value: obj, | |
| indentation, | |
| }; | |
| return; | |
| } | |
| for (const propKey in obj) { | |
| const newPath = currentPath ? `${currentPath}.${propKey}` : propKey; | |
| recurse(obj[propKey], newPath, propKey, indentation + 1); | |
| } | |
| } | |
| recurse(json); | |
| return result; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment