Skip to content

FlashList v2 #1617

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 72 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
f9250db
Implement new RecyclerView prototype with enhanced features and React…
naqvitalha Apr 11, 2025
d15052c
Skip core swapping in tests
naqvitalha Apr 11, 2025
c1df78c
Add header offset to initial scroll
naqvitalha Apr 12, 2025
3803c96
Add index to CellRendererComponent
naqvitalha Apr 14, 2025
e939ec9
Remove completed TODO comments
naqvitalha Apr 14, 2025
d8d6a50
Fix memoization in samples
naqvitalha Apr 14, 2025
ce22254
temporarily ignore fixture ts errors
naqvitalha Apr 15, 2025
ed50508
Update yarn.lock
naqvitalha Apr 15, 2025
3d4dfbf
remove random in samples
naqvitalha Apr 16, 2025
bdfc16c
build fix
naqvitalha Apr 16, 2025
60c4b14
Fix and add new e2e tests
naqvitalha Apr 16, 2025
0a42793
pod update
naqvitalha Apr 16, 2025
ddbf758
Unit testing most of the code
naqvitalha Apr 17, 2025
5acbd43
Fix lint errors
naqvitalha Apr 17, 2025
c322d7c
Fix sticky overflow
naqvitalha Apr 17, 2025
affed2d
Add docs 1.x and further edits to current docs
naqvitalha Apr 17, 2025
b134c90
Fix chat flickers
naqvitalha Apr 17, 2025
fca97ac
Added TODO
naqvitalha Apr 17, 2025
ebd701b
Improve scrollToIndex alogrithm
naqvitalha Apr 18, 2025
349124a
version bump
naqvitalha Apr 18, 2025
1d8c733
Optimize scrollTo for non animated situations
naqvitalha Apr 19, 2025
ed912a4
v2.0.0-alpha.5
naqvitalha Apr 19, 2025
41431d5
Fix RTL horizontal lists
naqvitalha Apr 20, 2025
6ced0ee
Add known issue for horizontal RTL
naqvitalha Apr 21, 2025
a44ca51
v2.0.0-alpha.6
naqvitalha Apr 21, 2025
8384af7
Fix unwanted mounts on data addition to top
naqvitalha Apr 22, 2025
c528355
Add note about horizontal lists
naqvitalha Apr 22, 2025
a2ccc4b
Update usage guide
naqvitalha Apr 22, 2025
5615d05
Fix missing custom refresh control
naqvitalha Apr 22, 2025
89b3452
v2.0.0-alpha.7
naqvitalha Apr 22, 2025
a564e69
Fix memo in docs
naqvitalha Apr 22, 2025
1420d03
Reduce draw distance in chat sample
naqvitalha Apr 22, 2025
6cf6b8e
change load time log
naqvitalha Apr 22, 2025
c7eab40
Add new useMappingHelper hook for solving .map
naqvitalha Apr 23, 2025
a1c97df
Fix duplicate onStartReached calls
naqvitalha Apr 23, 2025
d7ba976
v2.0.0-alpha.8
naqvitalha Apr 23, 2025
5e5f39e
Fix flicker when switching to masonry from grid
naqvitalha Apr 24, 2025
d59d5e4
Fix rendering on web and add a sample
naqvitalha Apr 24, 2025
2f4416b
Add more improvements for web
naqvitalha Apr 24, 2025
35aefba
Fix e2e test
naqvitalha Apr 24, 2025
c3722f5
v2.0.0-alpha.9
naqvitalha Apr 24, 2025
e4dc5bd
Add delay in e2e
naqvitalha Apr 24, 2025
ab818ef
bump flash-list in web fixture
naqvitalha Apr 24, 2025
1a810f0
Improve scrollTo accuracy even more
naqvitalha Apr 25, 2025
978e966
Improve velocity tracking which was missing on web and iOS
naqvitalha Apr 27, 2025
cf547f3
Fix bugs introduced by buffer improvements
naqvitalha Apr 28, 2025
18fa370
Improve buffer with render time
naqvitalha Apr 28, 2025
b3e9f32
Add wait to e2e
naqvitalha Apr 28, 2025
b479a3d
Fix e2e flakiness
naqvitalha Apr 28, 2025
e6d0bb7
v2.0.0-alpha.10
naqvitalha Apr 28, 2025
c4ccac3
Add an auto scroll script
naqvitalha Apr 29, 2025
4eecb24
Fix sticky crash
naqvitalha Apr 30, 2025
bf3de26
Improve twitter sample
naqvitalha Apr 30, 2025
8334a53
Upgrade fixture to RN 0.79.1
naqvitalha May 1, 2025
ae5525f
Update readme
naqvitalha May 4, 2025
54994bf
Fix lint errors and cleanup
naqvitalha May 7, 2025
58d0e87
Code cleanup and add few more methods to FlashList's handler
naqvitalha May 7, 2025
58bbef9
Fix lint errors
naqvitalha May 7, 2025
6a8e37d
Add measureLayout mocks
naqvitalha May 8, 2025
8e2176a
Update reanimated doc a bit
naqvitalha May 8, 2025
2c90e58
bug fixes
naqvitalha May 8, 2025
ef287ac
Add/Fix tests
naqvitalha May 9, 2025
de1eaf5
Fix infinite render loop in Grid Layout
naqvitalha May 11, 2025
a1cee9d
Improve sticky header compute
naqvitalha May 11, 2025
f098e61
Link to v1 docs
naqvitalha May 12, 2025
89a155a
Refactor recycling manager to be faster and more efficient
naqvitalha May 14, 2025
427d772
Reconfigure benchmarks
naqvitalha May 14, 2025
0327881
Export RecyclerView as AnimatedFlashList
naqvitalha May 14, 2025
2450817
Change displayName of RecyclerView
naqvitalha May 14, 2025
ae7e3fc
export recyclerview as default FlashList for tests
naqvitalha May 14, 2025
6d7d4fc
v2.0.0-alpha.11
naqvitalha May 14, 2025
401167a
Improve sticky headers animated setup
naqvitalha May 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
"@shopify/jsx-no-complex-expressions": "off",
"@shopify/react-prefer-private-members": "off",
"eslint-comments/disable-enable-pair": "off",
"@shopify/strict-component-boundaries": "off",
"import/no-cycle": "off",
"import/no-named-as-default": "off",
"max-params": "off",
Expand All @@ -40,6 +41,12 @@ module.exports = {
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/consistent-indexed-object-style": "off",
"jsx-a11y/no-autofocus": "off",
"line-comment-position": "off",
"react/no-unused-prop-types": "off",
"no-negated-condition": "off",
"no-nested-ternary": "off",
"@babel/no-unused-expressions": "off",
"@typescript-eslint/ban-ts-comment": "off",
},
overrides: [
{
Expand Down
10 changes: 10 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ ruby ">= 2.6.10"
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1', '!= 1.15.2'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'

# Ruby 3.4.0 has removed some libraries from the standard library.
gem 'bigdecimal'
gem 'logger'
gem 'benchmark'
gem 'mutex_m'


source "https://rubygems.org"
7 changes: 7 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ GEM
json (>= 1.5.1)
atomos (0.1.3)
base64 (0.2.0)
benchmark (0.4.0)
bigdecimal (3.1.8)
claide (1.1.0)
cocoapods (1.14.3)
Expand Down Expand Up @@ -79,6 +80,7 @@ GEM
i18n (1.14.5)
concurrent-ruby (~> 1.0)
json (2.10.2)
logger (1.7.0)
minitest (5.23.1)
molinillo (0.8.0)
mutex_m (0.2.0)
Expand Down Expand Up @@ -111,7 +113,12 @@ PLATFORMS

DEPENDENCIES
activesupport (>= 6.1.7.5, != 7.1.0)
benchmark
bigdecimal
cocoapods (>= 1.13, != 1.15.2, != 1.15.1, != 1.15.0)
concurrent-ruby (< 1.3.4)
logger
mutex_m
xcodeproj (< 1.26.0)

RUBY VERSION
Expand Down
210 changes: 183 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
![FlashList Image](./FlashList.png)

<div align="center">
<a href="https://shopify.github.io/flash-list/">Website</a> •
<a href="https://discord.gg/k2gzABTfav">Discord</a> •
<a href="https://shopify.github.io/flash-list/docs/">Getting started</a> •
<a href="https://shopify.github.io/flash-list/docs/usage">Usage</a> •
<a href="https://shopify.github.io/flash-list/docs/performance-troubleshooting">Performance</a> •
<a href="https://shopify.github.io/flash-list/docs/fundamentals/performant-components">Writing performant components</a> •
<a href="https://shopify.github.io/flash-list/docs/known-issues">Known Issues</a>
<br><br>
# FlashList v2

**Fast & performant React Native list. No more blank cells.**
FlashList v2 has been rebuilt from the ground up for RN's new architecture and delivers fast performance, higher precision, and better ease of use compared to v1. We've achieved all this while moving to a JS-only solution! One of the key advantages of FlashList v2 is that it doesn't require any estimates. It also introduces several new features compared to v1.

Swap from FlatList in seconds. Get instant performance.
> ⚠️ **IMPORTANT:** FlashList v2.x has been designed to fully leverage the new architecture. **Old architecture will only be supported while FlashList v2 is in alpha/beta and will be dropped once it's ready.** When run on old architecture, we just fall back to v1.x which doesn't have any of the new features.

</div>
> ⚠️ **IMPORTANT:** FlashList v2.x is in alpha and may have some issues. Please report any issues or edge cases you run into. We're actively working on testing and optimizing v2 so some things might change in the final version. We also highly recommend using it with RN 0.78+ for optimal performance.

## React Native's new architecture support
### Old architecture / FlashList v1

FlashList v1 is compatible with React Native's new architecture however, we have a new version (v2) in alpha that fully leverages the new architecture and comes with more features. Click [here](https://github.com/Shopify/flash-list/tree/new-rlv-prototype?tab=readme-ov-file#flashlist-v2) to know more.
If you're running on old architecture or using FlashList v1.x, you can access the documentation specific to v1 here: [FlashList v1 Documentation](https://shopify.github.io/flash-list/docs/1.x/).

### Web support

FlashList v2 has web support. Most of the features should work but we're not actively testing it right now. If you run into an issue, please raise it on GitHub.

## Installation

Add the package to your project via `yarn add @shopify/flash-list` and run `pod install` in the `ios` directory.
Add the package to your project via `yarn add @shopify/flash-list@alpha` and run `pod install` in the `ios` directory.

## Usage

We recommend reading the detailed documentation for using `FlashList` [here](https://shopify.github.io/flash-list/docs/usage).

But if you are familiar with [FlatList](https://reactnative.dev/docs/flatlist), you already know how to use `FlashList`. You can try out `FlashList` by changing the component name and adding the `estimatedItemSize` prop or refer to the example below:
But if you are familiar with [FlatList](https://reactnative.dev/docs/flatlist), you already know how to use `FlashList`. You can try out `FlashList` by changing the component name or refer to the example below:

```jsx
import React from "react";
Expand All @@ -49,20 +43,182 @@ const MyList = () => {
<FlashList
data={DATA}
renderItem={({ item }) => <Text>{item.title}</Text>}
estimatedItemSize={200}
/>
);
};
```

To avoid common pitfalls, you can also follow these steps for migrating from `FlatList`, based on our own experiences:

1. Switch from `FlatList` to `FlashList` and render the list once. You should see a warning about missing `estimatedItemSize` and a suggestion. Set this value as the prop directly.
2. **Important**: Scan your [`renderItem`](https://shopify.github.io/flash-list/docs/usage/#renderitem) hierarchy for explicit `key` prop definitions and remove them. If you’re doing a `.map()` use indices as keys.
3. Check your [`renderItem`](https://shopify.github.io/flash-list/docs/usage/#renderitem) hierarchy for components that make use of `useState` and verify whether that state would need to be reset if a different item is passed to that component (see [Recycling](https://shopify.github.io/flash-list/docs/recycling))
4. If your list has heterogenous views, pass their types to `FlashList` using [`getItemType`](https://shopify.github.io/flash-list/docs/usage/#getitemtype) prop to improve performance.
5. Do not test performance with JS dev mode on. Make sure you’re in release mode. `FlashList` can appear slower while in dev mode due to a small render buffer.
## New props

- `masonry`: Enable masonry layout for grid-like interfaces with varying item heights.

```jsx
<FlashList
data={data}
masonry
numColumns={3}
renderItem={({ item }) => <MasonryItem item={item} />}
/>
```

- `optimizeItemArrangement`: When enabled, masonry layout will try to reduce differences in column height by modifying item order.
- `onStartReached`: Called when the scroll position gets within `onStartReachedThreshold` of the start of the content.

```jsx
<FlashList
data={messageData}
onStartReached={() => loadOlderMessages()}
onStartReachedThreshold={0.1}
renderItem={({ item }) => <MessageItem message={item} />}
/>
```

- `onStartReachedThreshold`: How far from the start the top edge of the list must be to trigger `onStartReached`.
- `maxItemsInRecyclePool`: Maximum number of items in the recycle pool (Not required unless the number of item types is huge).
- `style`: Style for the FlashList's parent container. We highly recommend not adding padding which can impact the size of the ScrollView inside. We operate on the assumption that the size of parent view and ScrollView is the same. In most cases, `contentContainerStyle` should be enough so avoid using this.
- `maintainVisibleContentPosition`: Configuration for maintaining scroll position when content changes:
- `disabled`: Set to true to disable this feature (enabled by default).
- `autoscrollToTopThreshold`: Automatically scroll to maintain position when content is added at the top.
- `autoscrollToBottomThreshold`: Automatically scroll to maintain position when content is added at the bottom.
- `startRenderingFromBottom`: If true, initial render will start from the bottom, useful for chat interfaces.
```jsx
<FlashList
data={chatMessages}
maintainVisibleContentPosition={{
autoscrollToBottomThreshold: 0.2,
startRenderingFromBottom: true,
}}
renderItem={({ item }) => <ChatMessage message={item} />}
/>
```
- `onCommitLayoutEffect`: Called before layout is committed. Can be used to measure list and make changes before paint. Doing setState inside the callback can lead to infinite loops. Make sure FlashList's props are memoized.

## Deprecated (will be removed after alpha/beta)

- `estimatedItemSize`: No longer used.
- `estimatedListSize`: No longer used.
- `estimatedFirstItemOffset`: No longer used.
- `inverted`: We have added `maintainVisibleContentPosition` support, so we don't want to maintain inverted mode.
- `onBlankArea`: We don't have plans to add or continue supporting this prop.
- `disableHorizontalListHeightMeasurement`: No longer needed.
- `disableAutoLayout`: There's no auto layout in v2.
- `MasonryFlashList` will be replaced by `masonry` prop.
- `getColumnFlex` from `MasonryFlashList` will not be supported in FlashList v2 with `masonry` prop.

## Changed props

- `overrideItemLayout`: This used to allow a way to change the span of items and provide size estimates. In v2, span is supported, but we no longer read the size estimates.
```jsx
<FlashList
data={gridData}
numColumns={2}
overrideItemLayout={(layout, item) => {
layout.span = item.span; // Set span
}}
renderItem={({ item }) => <GridItem item={item} />}
/>
```

## New features

- `masonry` is now a prop on FlashList. It's now also possible to use `overrideItemLayout` with `masonry`.
- `maintainVisibleContentPosition` is available and now enabled by default. We use this to reduce visible glitches as much as possible. Chat apps without inverted will also be possible. Please note that if you plan on adding a large number of rows on top of the list, then you may want to increase the drawDistance on the list.
- `onStartReached` callback is now available with a configurable threshold.
- We've also added support for RTL layouts.

## Improvements

- `scrollToIndex` and `scrollToItem` are much more precise.
- Scrolling upwards after orientation change doesn't cause layout glitches. The same is true for scrolling to items and scrolling upwards.
- `stickyHeaders` use an Animated implementation, so minor gaps between them while scrolling aren't visible anymore.
- FlashList does not ask for any estimates, which makes it much easier to use.
- Horizontal Lists are much improved, and items can also resize within the lists. We no longer render an extra item to measure list height.
- In Grid layout, if side-by-side items have different heights, then the shorter item will match the height of the tallest item. This wasn't possible in v1.
- The ref of FlashList has many more useful methods like `getVisibleIndices` and `getLayout`.
- `contentContainerStyle` prop is fully supported now.

## Things to know

- `keyExtractor` is important to prevent glitches due to item layout changes when going upwards. We highly recommend having a valid `keyExtractor` with v2.
- Avoid adding keys directly to components which can break recycling. Same as v1. More info [here](https://shopify.github.io/flash-list/docs/fundamentals/performant-components/#remove-key-prop).
- `useLayoutState`: This is similar to `useState` but communicates the change in state to FlashList. It's useful if you want to resize a child component based on a local state. Item layout changes will still be detected using `onLayout` callback in the absence of `useLayoutState`, which might not look as smooth on a case-by-case basis.

```jsx
import { useLayoutState } from "@shopify/flash-list";

const MyItem = ({ item }) => {
const [isExpanded, setIsExpanded] = useLayoutState(false);
const height = isExpanded ? 150 : 80;

return (
<Pressable onPress={() => setIsExpanded(!isExpanded)}>
<View style={{ height, padding: 16 }}>
<Text>{item.title}</Text>
</View>
</Pressable>
);
};
```

- `useRecyclingState`: Similar to `useState` but accepts a dependency array. On change of deps, the state gets reset without an additional `setState` call. Useful for maintaining local item state if really necessary. It also has the functionality of `useLayoutState` built in.

```jsx
import { useRecyclingState } from "@shopify/flash-list";

const GridItem = ({ item }) => {
const [isExpanded, setIsExpanded] = useRecyclingState(
false,
[item.id],
() => {
// runs on reset. Can be used to reset scroll positions of nested horizontal lists
}
);
const height = isExpanded ? 100 : 50;

return (
<Pressable onPress={() => setIsExpanded(!isExpanded)}>
<View style={{ height, backgroundColor: item.color }}>
<Text>{item.title}</Text>
</View>
</Pressable>
);
};
```

- `useMappingHelper`: Returns a function that helps create optimal mapping keys for items when using `.map()` in your render methods. Using this ensures optimized recycling and performance for FlashList.

```jsx
import { useMappingHelper } from "@shopify/flash-list";

const MyComponent = ({ items }) => {
const { getMappingKey } = useMappingHelper();

return (
<FlashList
data={items}
renderItem={({ item }) => <ItemComponent item={item} />}
/>
);
};

// When mapping over items inside components:
const NestedList = ({ items }) => {
const { getMappingKey } = useMappingHelper();

return (
<View>
{items.map((item, index) => (
<Text key={getMappingKey(index, item.id)}>{item.title}</Text>
))}
</View>
);
};
```

- If you're nesting horizontal FlashLists in vertical lists, we highly recommend the vertical list to be FlashList too. We have optimizations to wait for child layout to complete which can improve load times.
- For chat apps, consider increasing drawDistance to 500 or higher if you're going to add a lot of items to the top. Higher drawDistance can remove some flickers. 500-1000 for chat can be okay. We would like to hear from you if you run into issues.
- Memoizing props passed to FlashList is more important in v2. v1 was more selective about updating items, but this was often perceived as a bug by developers. We will not follow that approach and will instead allow developers to ensure that props are memoized. We will stop re-renders of children wherever it is obvious.

## App / Playground

The [fixture](https://github.com/Shopify/flash-list/tree/main/fixture) is an example app showing how to use the library.
The [fixture](https://github.com/Shopify/flash-list/tree/new-rlv-prototype/fixture) is an example app showing how to use the library.
7 changes: 2 additions & 5 deletions documentation/docs/fundamentals/performant-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ There's lots of optimizations that are applicable for _any_ React Native compone
Always profile performance in the release mode. `FlashList`'s performance between JS dev and release mode differs greatly.
:::

### `estimatedItemSize`

Ensure [`estimatedItemSize`](/usage#estimateditemsize) is as close as possible to the real average value - see [here](/estimated-item-size#how-to-calculate) how to properly calculate the value for this prop.

### Remove `key` prop

:::warning
Expand Down Expand Up @@ -182,8 +178,9 @@ const MyHeavyComponent = () => {
return ...;
};

const MemoizedMyHeavyComponent = memo(MyHeavyComponent);

const MyItem = ({ item }: { item: any }) => {
const MemoizedMyHeavyComponent = memo(MyHeavyComponent);
return (
<>
<MemoizedMyHeavyComponent />
Expand Down
14 changes: 5 additions & 9 deletions documentation/docs/fundamentals/recycling.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,14 @@ title: Recycling
slug: /recycling
---

One important thing to understand is how `FlashList` works under the hood. When an item gets out of the viewport, instead of being destroyed, the component is re-rendered with a different `item` prop. For example, if you make use of `useState` in a reused component, you may see state values that were set for that component when it was associated with a different item in the list, and would then need to reset any previously set state when a new item is rendered:
One important thing to understand is how `FlashList` works under the hood. When an item gets out of the viewport, instead of being destroyed, the component is re-rendered with a different `item` prop. For example, if you make use of `useState` in a reused component, you may see state values that were set for that component when it was associated with a different item in the list, and would then need to reset any previously set state when a new item is rendered. FlashList now comes with `useRecyclingState` hook that can reet the state automatically without an additional render.

```tsx
const MyItem = ({ item }) => {
const lastItemId = useRef(item.someId);
const [liked, setLiked] = useState(item.liked);
if (item.someId !== lastItemId.current) {
lastItemId.current = item.someId;
setLiked(item.liked);
}
// value of liked is reset if deps array changes. The hook also accepts a callback to reset anything else if required.
const [liked, setLiked] = useRecyclingState(item.liked, [item.someId], () => {
// callback
});

return (
<Pressable onPress={() => setLiked(true)}>
Expand All @@ -23,6 +21,4 @@ const MyItem = ({ item }) => {
};
```

This follows advice in the [React Hooks FAQ on implementing getDerivedStateFromProps](https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops). Ideally your component hierarchy returned from [renderItem](../fundamentals/usage.md#renderitem) should not make use of `useState` for best performance.

When optimizing your item component, try to ensure as few things as possible have to be re-rendered and recomputed when recycling.
Loading
Loading