High-performance list for React Native.
- π± True native
UICollectionViewon iOS - π€ True native
RecyclerViewon android - π Synchronous rendering of react components using "worklet" function components
- π Platform native animations out of the box for list item transitions
- π Low memory usage due to true native view recycling
demo.MP4
Warning
Using this library requires you to use react-native-worklets bundle mode.
- Please follow the setup instructions of it first, and make sure your app works before using react-native-list!
- You need at least version 0.9.1!
Caution
Right now you will have to patch react-native-worklets. See the patch file here and ask your friendly AI to apply it:
Once those PRs are landed upstream it will no longer be necessary:
Once that's out of the way, you can start with the regular setup of the library:
bun add react-native-list@alphaSimply wrap your config with getReactNativeListMetroConfig in metro.config.js:
const { getDefaultConfig } = require("expo/metro-config");
const rnlistConfig = getReactNativeListMetroConfig(getDefaultConfig());
module.exports = rnlistConfig;Note
If you're using expo you need to make sure to enable inline requires in your metro config:
const config = {
transformer: {
getTransformOptions: async () => ({
transform: {
// Bundle mode only works with inline support
// See: https://github.com/software-mansion/react-native-reanimated/issues/8904
inlineRequires: true,
},
}),
},
};If you don't see a metro.config.js in your project, see expo's documentation on modifying metro.
import { List, useLinearListLayout, useListDataSource } from "react-native-list";
import type { ListItem, ListRenderers } from "react-native-list";
type TextItem = ListItem<
// The type of your list item
"text",
// The data shape for that list item:
{
text: string;
}
>;
type ImageItem = ...
type Items = TextItem | ImageItem
// Provide render functions for your item types:
const renderers: ListRenderers<Items> = {
text: {
renderItemWorklet: ({ item }) => {
"worklet"; // π Note: our render function is a worklet!
return (
// Though being in a worklet, you can use any component you like in here
<View
style={{
justifyContent: "center",
paddingHorizontal: 16,
backgroundColor: "#f6f8fa",
}}
>
// Note: there are two phases: 1. View creation (without data), 2. binding data to the views
// that's why item.data can be undefined because the native list is requesting to
// create the view hierarchy, but without any data yet.
<Text>{item.data?.text}</Text>
</View>
);
},
},
image: ...
};
export function ExampleList() {
const dataSource = useListDataSource<Items>({
data: [...],
});
const layout = useLinearListLayout({
itemSpacing: 8,
});
return (
<List
dataSource={dataSource}
layout={layout}
renderers={renderers}
style={{
flex: 1,
}}
/>
);
}XXXX TODO
Scrolling as fast as possible. Release build. iPhone 13 Pro.
left-legendlist-right-RNL.mp4
| Legend List | react-native-list | |
|---|---|---|
| FPS | 60 | 60 |
| Memory | 86.89mb | 86.88mb |
| Total CPU | 144.69% | 185.93% |
| Can blank | yes | no |
Performance difference is best described as this:
- With react-native-list you will never get a blank. However, instead, you could see UI thread drop frames if your item rendering is too heavy.
- With Legend List you may see blanks, however, its way less likely you will drop frames on the UI thread (instead your JS thread might be fully busy, but you won't really get to notice that on the UIs performance)
- For iOS when using dynamically sized items, try to use
iosConfig.estimatedItemSizeto roughly specify how many items will be visible in the view port. This can help a lot with performance. - When specifying sizes for items use
useLinearListLayout({})inset configs. Avoid setting a width in the styles that exceed the actual available view port width. - If you can, always provide item sizes upfront. With that the performance will be unbeatable.
- In your item render function, when you have no item data yet it is tempting to return early with
nullor just an empty<View />. However, this is super bad for performance. There are two phases for native lists. First is view creation, where you're expected to create the view hierarchy for your item - just not with any data yet (so that any data could be bind to it). The second phase is actually binding data to the view. This will result in a simple "update props" operation on the native side instead of needing to create a new view hierarchy. Example:
β Bad:
renderItemWorklet: ({ item }) => {
"worklet";
if (item == null) return <View />
return (
<View>
<Image src={item.image}>
<Text>{item.userName}</Text>
</View>
)β Good:
renderItemWorklet: ({ item }) => {
"worklet";
return (
<View>
<Image src={item?.image}>
<Text>{item?.userName ?? ""}</Text>
</View>
)First clone init the submodules:
git submodule update --init --recursiveRegenerate native Nitro specs after changing files in src/specs or nitro.json:
bun specsRun TypeScript checks:
bun run typecheck