Skip to content

hannojg/react-native-list

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

148 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

React Native List

High-performance list for React Native.

  • πŸ“± True native UICollectionView on iOS
  • πŸ€– True native RecyclerView on 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

Installation

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@alpha

Configuring Metro

Simply 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.

Simple example

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,
      }}
    />
  );
}

API

XXXX TODO

Benchmark

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)

Known performance pitfalls

  • For iOS when using dynamically sized items, try to use iosConfig.estimatedItemSize to 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 null or 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>
  )

Development

First clone init the submodules:

git submodule update --init --recursive

Regenerate native Nitro specs after changing files in src/specs or nitro.json:

bun specs

Run TypeScript checks:

bun run typecheck

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors