Skip to content

Usage

Legend List is a recycling list view that can massively increase performance of rendering long lists. Rather than rendering every item in the list, it only renders the items that are in view, which significantly reduces the amount of items that need to render.

Legend List is a drop-in replacement for FlatList or FlashList. So since you’re likely coming from one of those, we’ll start with a guide on how to switch.

Switch from FlashList

If you’re coming from FlashList, in most cases you can just rename the component and it will work as expected. But note that Legend List does not recycle items by default, so to match FlashList’s behavior you can enable recycleItems.

return (
<FlashList
<LegendList
data={items}
renderItem={({ item }) => <Text>{item.title}</Text>}
estimatedItemSize={320}
recycleItems
/>
)

Switch from FlatList

If you’re coming from FlatList, you’ll just want to add at least one prop, the estimatedItemSize. Legend List automatically lays out your items in a very high performance way, and a hint for what size the items are likely to be will help it be as fast as possible.

return (
<FlatList
<LegendList
data={items}
renderItem={({ item }) => <Text>{item.title}</Text>}
estimatedItemSize={320}
/>
)

If you first render a LegendList without an estimatedItemSize it will suggest an optimal item size based on the rendered items, so that can help you find the right number.

Guide

Legend List is very optimized by default, so it may already be working well without any configuration. But these are some common ways to improve your list behavior.

Estimate item sizes

estimatedItemSize?: number;
getEstimatedItemSize?: (index: number, item: T) => number;

It’s important to provide an estimatedItemSize (if items are the same size or all dynamic sizes) or getEstimatedItemSize (if items are different known sizes). Legend List uses this as the default item size, then as items are rendered it updates their positions with the actual size. So getting this estimate as close as possible to the real size will reduce layout shifting and blank spaces as items render. If not provided it will use 100px as the default.

Use keyExtractor

keyExtractor?: (item: T, index: number) => string;

The keyExtractor prop lets Legend List save item layouts by key, so that if the data array changes it can reuse previous layout information and only update the changed items. Without keyExtractor, item sizes will reset to their default whenever data changes. So it is very recommended to have a keyExtractor if data ever changes. If your items are a fixed size, providing a keyExtractor that returns the index will tell it to reuse size information.

Set a drawDistance

drawDistance?: number

The drawDistance (defaults to 250) is the buffer size in pixels above and below the viewport that will be rendered in advance. So for example if your screen is 2000px tall and your draw distance is 1000, then it will render double your screen size, from -1000px above the viewport to 1000px below the viewport.

This can help reduce the amount of blank space while scrolling quickly. But if your items are computationally expensive, it may reduce performance because more items are rendering at once. So you should experiment with it to find the most optimal behavior for your app.

Recycling items

recycleItems?: boolean

Legend List has an optional recycleItems prop which enables view recycling. This will reuse the component rendered by your renderItem function. This can be a big performance optimization because it does not need to destroy/create views while scrolling. But it also reuses any local state, which can cause some weird behavior that may not be desirable depending on your app. But see the next section for recycling hooks to make that easier.

So there are some tradeoffs with recycling:

  • 👍 If you have items with no state then recycling should be great
  • 👎 If you have simple items with complex state then it may be more trouble than it’s worth
  • 👍 If you have heavy items with complex state then working around the state recycling may be worth it for the performance gains

Recycling hooks

useRecyclingEffect: (effect: (info: LegendListRecyclingState) => void | (() => void)) => void;
useRecyclingState: <T>(updateState: ((info: LegendListRecyclingState) => T) | T) => [T, Dispatch<T>];

renderItem receives two hooks to help you manage the recycling.

  1. useRecyclingState automatically resets the state when it recycles into a new item
  2. useRecyclingEffect can be used to reset any side effects when an item gets recycled.
export function ItemComponent({ item, useRecyclingEffect, useRecyclingState }) {
// Like useState but it resets when the item is recycled
const [isExpanded, setIsExpanded] = useRecyclingState(() => false);
// A callback when the item is recycled into a new item
useRecyclingEffect?.(({ item, prevItem, index, prevIndex }) => {
// Reset any side effects from the previous item
refSwipeable?.current?.close();
refVideo?.current?.reset();
});
// ...
}

Maintain Visible Content Position

maintainVisibleContentPosition?: boolean;

The maintainVisibleContentPosition prop automatically adjusts item positions when items are added/removed/resized above the viewport so that there is no shift in the visible content. This is very helpful for some scenarios, but if you have a static list of fixed sized items you probably don’t need it.

  • If items get added/removed/resized above the viewport, items will not move on screen
  • When using initialScrollOffset or initialScrollIndex, items will not jump around when scrolling up if they’re different sizes than the estimate
  • When scrolling to an index far down the list and then back up, items will not jump around as they layout

Chat interfaces without inverse

alignItemsAtEnd?: boolean;
maintainScrollAtEnd?: boolean;
maintainScrollAtEndThreshold?: number;

In other list libraries if you wanted items to start scrolling from the bottom, you’d need to use an inverted prop, which would apply a negative scale transform. But that causes a lot of weird issues, so Legend List explicitly does not do that.

Instead, to align items at the end you can just use the alignItemsAtEnd prop, which will apply padding above items to fill the screen and stick them to the bottom.

The maintainScrollAtEnd prop will check if you are already scrolled to the bottom when data changes, and if so it keeps you scrolled to the bottom.

The maintainScrollAtEndThreshold prop (which defaults to 0.1) defines what percent of the screen counts as the bottom.

So using Legend List for a chat interface would look like this:

<LegendList
data={items}
renderItem={({ item }) => <Text>{item.title}</Text>}
estimatedItemSize={320}
alignItemsAtEnd
maintainScrollAtEnd
maintainScrollAtEndThreshold={0.1}
/>

Two-way infinite scrolling

onStartReached?: ((info: { distanceFromStart: number }) => void) | null | undefined;
onEndReached?: ((info: { distanceFromEnd: number }) => void) | null | undefined;

These callbacks fire when you scroll to the top or bottom of a list. This can be used to load more data in either direction. In a typical list you’ll likely just use onEndReached to load more data when the users scrolls to the bottom.

If you have a chat-like interface you may want to load more messages as you scroll up, and you can use onStartReached for that. If you are doing that, you will very likely want to use maintainVisibleContentPosition so that the items loading above don’t shift the viewport down.

Props

alignItemsAtEnd

alignItemsAtEnd?: boolean;

Aligns to the end of the screen, so if there’s only a few items there will be enough padding at the top to make them appear to be at the bottom. See Chat interfaces without inverse for more.

data

data: ItemT[];

An array of the items to render. This can also be an array of keys if you want to get the item by key in renderItem.

drawDistance

drawDistance?: number;

The drawDistance (defaults to 250) is the buffer size in pixels above and below the viewport that will be rendered in advance. See drawDistance for more.

estimatedItemSize

estimatedItemSize?: number;

An estimated size for all items which is used to estimate the list layout before items actually render. If you don’t provide this, it will log a suggested value for optimal performance.

getEstimatedItemSize

getEstimatedItemSize?: (index: number, item: ItemT) => number;

An estimated size for each item which is used to estimate the list layout before items actually render. If you don’t provide this, it will log a suggested value for optimal performance.

initialScrollIndex

initialScrollIndex?: number;

Start scrolled with this item at the top.

initialScrollOffset

initialScrollOffset?: number;

Start scrolled to this offset.

ItemSeparatorComponent

ItemSeparatorComponent?: React.ComponentType<any>;

Rendered in between each item, but not at the top or bottom. By default, highlighted and leadingItem Called when the viewability of rows changes, as defined by the viewabilityConfig prop.t), or a React element.

See React Native Docs.

keyExtractor

keyExtractor?: (item: ItemT, index: number) => string;

Highly recommended. The keyExtractor prop lets Legend List save item layouts by key, so that if the data array changes it can reuse previous layout information and only update the changed items. See Use key extractor

ListEmptyComponent

ListEmptyComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;

Called when the viewability of rows changes, as defined by the viewabilityConfig prop.eComponent />).

See React Native Docs.

ListEmptyComponentStyle

ListEmptyComponentStyle?: StyleProp<ViewStyle> | undefined;
Called when the viewability of rows changes, as defined by the viewabilityConfig prop.al View for ListEmptyComponent.
See [React Native Docs](https://reactnative.dev/docs/flatlist#listemptycomponent).
### ListFooterComponent
```ts
ListFooterComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;

Called when the viewability of rows changes, as defined by the viewabilityConfig prop.eComponent />).

See React Native Docs.

ListFooterComponentStyle

ListFooterComponentStyle?: StyleProp<ViewStyle> | undefined;
Called when the viewability of rows changes, as defined by the viewabilityConfig prop.al View for ListFooterComponent.
See [React Native Docs](https://reactnative.dev/docs/flatlist#listfootercomponentstyle).
### ListHeaderComponent
```ts
ListHeaderComponent?: React.ComponentType<any> | React.ReactElement | null | undefined;

Called when the viewability of rows changes, as defined by the viewabilityConfig prop.nt />).

See React Native Docs.

ListHeaderComponentStyle

ListHeaderComponentStyle?: StyleProp<ViewStyle> | undefined;
Called when the viewability of rows changes, as defined by the viewabilityConfig prop.al View for ListHeaderComponent.
See [React Native Docs](https://reactnative.dev/docs/flatlist#listheadercomponentstyle).
### maintainScrollAtEnd
```ts
maintainScrollAtEnd?: boolean;

This will check if you are already scrolled to the bottom when data changes, and if so it keeps you scrolled to the bottom.

See Chat interfaces without inverse for more.

maintainScrollAtEndThreshold

maintainScrollAtEndThreshold?: number;

This defines what percent of the screen counts as the bottom. Defaults to 0.1.

See Chat interfaces without inverse for more.

maintainVisibleContentPosition

maintainVisibleContentPosition?: boolean;

Automatically adjust item positions when items are added/removed/resized above the viewport so that there is no shift in the visible content.

See Maintain Visible Content Position for more.

numColumns

numColumns?: number;

Multiple columns will zig-zag like a flexWrap layout. Rows will take the maximum height of their columns, so items should all be the same height - masonry layouts are not supported.

onEndReached

onEndReached?: ((info: { distanceFromEnd: number }) => void) | null | undefined;

A callback that’s called only once when scroll is within onEndReachedThreshold of the bottom of the list. It resets when scroll goes above the threshold and then will be called again when scrolling back into the threshold.

onEndReachedThreshold

onEndReachedThreshold?: number | null | undefined;

The distance from the end as a percentage that the scroll should be from the end to trigger onEndReached. It is multiplied by screen size, so a value of 0.5 will trigger onEndReached when scrolling to half a screen from the end.

onStartReached

onStartReached?: ((info: { distanceFromStart: number }) => void) | null | undefined;

A callback that’s called only once when scroll is within onStartReachedThreshold of the top of the list. It resets when scroll goes below the threshold and then will be called again when scrolling back into the threshold.

onStartReachedThreshold

onStartReachedThreshold?: number | null | undefined;

The distance from the start as a percentage that the scroll should be from the end to trigger onStartReached. It is multiplied by screen size, so a value of 0.5 will trigger onStartReached when scrolling to half a screen from the start.

onViewableItemsChanged

onViewableItemsChanged?: OnViewableItemsChanged | undefined;

Called when the viewability of rows changes, as defined by the viewabilityConfig prop.

See React Native Docs.

recycleItems

recycleItems?: boolean;

renderItem

renderItem?: (props: LegendListRenderItemProps<ItemT>) => ReactNode;

viewabilityConfig

viewabilityConfig?: ViewabilityConfig;

Configuration for when to update the onViewableItemsChanged callback.

See React Native Docs.

viewabilityConfigCallbackPairs

viewabilityConfigCallbackPairs?: ViewabilityConfigCallbackPairs | undefined;

List of ViewabilityConfig/onViewableItemsChanged pairs. A specific onViewableItemsChanged will be called when its corresponding ViewabilityConfig’s conditions are met.

See React Native Docs.