Skip to content

Instantly share code, notes, and snippets.

@DanielSRS
Created August 1, 2025 16:37
Show Gist options
  • Select an option

  • Save DanielSRS/d91a44d40093d5c5cf1b0051ef4ac120 to your computer and use it in GitHub Desktop.

Select an option

Save DanielSRS/d91a44d40093d5c5cf1b0051ef4ac120 to your computer and use it in GitHub Desktop.
Experimenting rendering large element trees
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