**Fast & performant React Native list. No more blank cells.**
Swap from FlatList in seconds. Get instant performance.
# FlashList v2
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. To know more about what's new in v2 click [here](https://shopify.github.io/flash-list/docs/v2-changes).
> ⚠️ **IMPORTANT:** FlashList v2.x has been designed to be new architecture only and will not run on old architecture. 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/).
## Why use FlashList?
### 🚀 Superior Performance
- No more blank cells: FlashList uses view recycling to ensure smooth scrolling without visible blank areas.
- Fast initial render: Optimized for quick first paint.
- Efficient memory usage: Recycles views instead of destroying them, reducing memory overhead.
- Supports view types: Great performance even if different types of components make up the list.
- Dynamic sizes: Super fast and doesn't need any estimates.
### 🎯 Developer Experience
- Drop-in replacement for FlatList: Simply change the component name - if you know FlatList, you already know FlashList.
- No size estimates required in v2: Unlike v1, FlashList v2 automatically handles item sizing.
- Type-safe: Full TypeScript support with comprehensive type definitions.
### 📱 Advanced Features
- Masonry layout support: Create Pinterest-style layouts with varying item heights and column spans.
- Maintain visible content position: Automatically handles content shifts when adding items (enabled by default in v2).
- Multiple recycling pools: Optimizes performance for lists with different item types using `getItemType`.
- Built for React Native's new architecture: FlashList v2 is designed specifically for the new architecture.
### ⚡ Real-world Benefits
- Reduced frame drops: Maintains 60 FPS even with complex item components.
- Lower CPU usage: Efficient recycling reduces computational overhead.
- Smoother scrolling: Predictable performance even with thousands of items.
- JS-only solution in v2: No native dependencies, making it easier to maintain while delivering fast performance.
## Installation
Add the package to your project via `yarn add @shopify/flash-list`.
## 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 or refer to the example below:
```jsx
import React from "react";
import { View, Text } from "react-native";
import { FlashList } from "@shopify/flash-list";
const DATA = [
{
title: "First Item",
},
{
title: "Second Item",
},
];
const MyList = () => {
return (
{item.title}}
/>
);
};
```
To avoid common pitfalls, you can also follow these [`steps`](https://shopify.github.io/flash-list/docs/usage#migration-steps) for migrating from `FlatList`.
## App / Playground
The [fixture](./fixture/) is an example app showing how to use the library.
---
### CHANGELOG (CHANGELOG.md)
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
- Introduce optional view offset param for initial scroll position
- https://github.com/Shopify/flash-list/pull/1870
- Add sticky header offset and sticky header backgrounds
- https://github.com/Shopify/flash-list/pull/1953
## [1.7.6] - 2025-03-19
- Fix React 18 ref error
- https://github.com/Shopify/flash-list/pull/1573
## [1.7.5] - 2025-03-13
- Fix React 19 ref error
- https://github.com/Shopify/flash-list/pull/1554
## [1.7.3] - 2025-01-30
- Changes for RN 0.77 support
- https://github.com/Shopify/flash-list/pull/1484
## [1.7.2] - 2024-11-07
- Update sticky headers when data changes without changing stickyHeaderIndices updated
- https://github.com/Shopify/flash-list/pull/1267
## [1.7.1] - 2024-08-07
- Add recomputeViewableItems method
- https://github.com/Shopify/flash-list/pull/1296
- Fix first sticky header is not rendering when data changed
- https://github.com/Shopify/flash-list/issues/814
## [1.7.0] - 2024-07-03
- Update internal dependency and fixture app to `react-native@0.72`.
- https://github.com/Shopify/flash-list/pull/1076
- Fix stale reference to onScroll and onLoad
- https://github.com/Shopify/flash-list/pull/1112
- Fix Android RN 0.73 namespace requirement
- https://github.com/Shopify/flash-list/pull/1216
- New architecture support
- https://github.com/Shopify/flash-list/pull/550
- Upgrade recyclerlistview to v4.2.1
- https://github.com/Shopify/flash-list/pull/1236
## [1.6.4] - 2024-03-18
- Fix `ItemSeparatorComponent ` position for the `inverted` flashlist
- https://github.com/Shopify/flash-list/pull/1046
- Do not show render size unusable warning in better layout changes
- https://github.com/Shopify/flash-list/pull/1092
## [1.6.3] - 2023-11-09
- Changes for RN 0.73 support
- https://github.com/Shopify/flash-list/pull/930
## [1.6.2] - 2023-10-19
- Move shouldRefreshWithAnchoring configuration so it is possible to disable it from outside FlashList by invalidating layout
- https://github.com/Shopify/flash-list/pull/935
## [1.6.1] - 2023-09-14
- Prevent an expired layout provider from being used again
- https://github.com/Shopify/flash-list/pull/915
## [1.6.0] - 2023-09-13
- Update types to match `react-native@0.72` view types.
- https://github.com/Shopify/flash-list/pull/890
- Add option to clear cached layouts on update
- https://github.com/Shopify/flash-list/pull/910
## [1.5.0] - 2023-07-12
- Update kotlin version to 1.8.10 for RN 0.72 compatibility
- https://github.com/Shopify/flash-list/pull/865
## [1.4.3] - 2023-04-24
- Fix definition conflicts with previous value
- https://github.com/Shopify/flash-list/pull/795
- Fix Android unit test
- https://github.com/Shopify/flash-list/pull/815
- Fix performance issues with inverted lists on Android
- https://github.com/Shopify/flash-list/pull/819
## [1.4.2] - 2023-03-20
- Apply layout correction only to consecutive cells
- https://github.com/Shopify/flash-list/pull/788
## [1.4.1] - 2023-01-24
- Prevent overflow of sticky headers
- https://github.com/Shopify/flash-list/pull/714
- Skip footer correction when layout correction is skipped
- https://github.com/Shopify/flash-list/pull/743
## [1.4.0] - 2022-11-07
- Add content padding support to FlashList
- https://github.com/Shopify/flash-list/pull/626
- Upgrade recyclerlistview to v4.2.0
- https://github.com/Shopify/flash-list/pull/660
## [1.3.1] - 2022-10-11
- Expose `columnIndex` and `columnSpan` to `MasonryFlashList.renderItem`
- https://github.com/Shopify/flash-list/pull/625
## [1.3.0] - 2022-09-26
- Added `MasonryFlashList` which adds support for rendering masonry layouts
- https://github.com/Shopify/flash-list/pull/587
## [1.2.2] - 2022-09-06
- Fixes type checking error in `AutoLayoutView` due to `children` not being an explicit type
- https://github.com/Shopify/flash-list/pull/567
## [1.2.1] - 2022-08-03
- Fixed crash when `estimatedListSize` is used in an empty list
- https://github.com/Shopify/flash-list/pull/546
## [1.2.0] - 2022-07-18
- Fixed out of bound read from data
- https://github.com/Shopify/flash-list/pull/523
- Added JS only fallbacks for unsupported platforms
- https://github.com/Shopify/flash-list/pull/518
- Added footer correction in AutoLayoutView
- https://github.com/Shopify/flash-list/pull/519
- Added `viewPosition` and `viewOffset` support to scrollTo methods
- https://github.com/Shopify/flash-list/pull/521
- Fix inverted mode while being horizontal
- https://github.com/Shopify/flash-list/pull/520
- Upgrade recyclerlistview to v4.1.1
- https://github.com/Shopify/flash-list/pull/526
## [1.1.0] - 2022-07-06
- Added render target info to `renderItem` callback
- https://github.com/Shopify/flash-list/pull/454
- Add Apple TV support
- https://github.com/Shopify/flash-list/pull/511
- Clarify installation instructions in Expo projects
- https://github.com/Shopify/flash-list/pull/497
- Upgrade recyclerlistview to v4.0.1
- https://github.com/Shopify/flash-list/pull/507
- Add tslib as a dependency
- https://github.com/Shopify/flash-list/pull/514
## [1.0.4] - 2022-07-02
- Build fix for Android projects having `kotlinVersion` defined in `build.gradle`.
- Allow providing an external scrollview.
- https://github.com/Shopify/flash-list/pull/502
## [1.0.3] - 2022-07-01
- Add kotlin-gradle-plugin to buildscript in project build.gradle
- https://github.com/Shopify/flash-list/pull/481
## [1.0.2] - 2022-06-30
- Minor changes
## [1.0.1] - 2022-06-30
- `data` prop change will force update items only if `renderItem` is also updated
- https://github.com/Shopify/flash-list/pull/453
## [1.0.0] - 2022-06-17
- Upgrade recyclerlistview to v3.3.0-beta.2
- https://github.com/Shopify/flash-list/pull/445
- Added web support
- https://github.com/Shopify/flash-list/pull/444
- Added `disableAutoLayout` prop to prevent conflicts with custom `CellRendererComponent`
- https://github.com/Shopify/flash-list/pull/452
## [0.6.1] - 2022-05-26
- Fix amending layout on iOS
- https://github.com/Shopify/flash-list/pull/412
- Define `FlashList` props previously inherited from `VirtualizedList` and `FlatList` explicitly
- https://github.com/Shopify/flash-list/pull/386
- Make `estimatedItemSize` optional
- https://github.com/Shopify/flash-list/pull/378
- Change `overrideItemType` prop name to `getItemType`
- https://github.com/Shopify/flash-list/pull/369
- Added `useBlankAreaTracker` hook for tracking blank area in production
- https://github.com/Shopify/flash-list/pull/411
- Added `CellRendererComponent` prop
- https://github.com/Shopify/flash-list/pull/362
- Added automatic height measurement for horizontal lists even when parent isn't deterministic
- https://github.com/Shopify/flash-list/pull/409
## [0.5.0] - 2022-04-29
- Fix finding props with testId
- https://github.com/Shopify/flash-list/pull/357
- Reuse cached layouts on orientation change
- https://github.com/Shopify/flash-list/pull/319
## [0.4.6] - 2022-04-13
- Match FlashList's empty list behavior with FlatList
- https://github.com/Shopify/flash-list/pull/312
## [0.4.5] - 2022-04-13
- Upgrade recyclerlistview to v3.2.0-beta.4
- https://github.com/Shopify/flash-list/pull/315
- Add viewability callbacks
- https://github.com/Shopify/flash-list/pull/301
- Calculate average item sizes automatically
- https://github.com/Shopify/flash-list/pull/296
## [0.4.4] - 2022-04-06
- Fix `FlashList` mock when no data is provided
- https://github.com/Shopify/flash-list/pull/295
## [0.4.3] - 2022-04-04
- Reduce number of render item calls
- https://github.com/Shopify/flash-list/pull/253
- Upgrade recyclerlistview to v3.2.0-beta.2
- https://github.com/Shopify/flash-list/pull/284
## [0.4.2] - 2022-04-04
- Minor changes
## [0.4.1] - 2022-03-29
- Crash fix for android activity switching (#256)
- https://github.com/Shopify/flash-list/pull/257
- initialScrollIndex, scrollTo methods will now account for size of header
- https://github.com/Shopify/flash-list/pull/194
- Added a new mock for easier testing of components with `FlashList`
- https://github.com/Shopify/flash-list/pull/236
## [0.4.0] - 2022-03-23
- Add support for layout animations
- https://github.com/Shopify/flash-list/pull/183
- Suppress recyclerlistview's bounded size exception for some missing cases.
- https://github.com/Shopify/flash-list/pull/192
- Expose reference to recyclerlistview and firstItemOffset
- https://github.com/Shopify/flash-list/pull/217
- recyclerlistview upgraded to v3.1.0-alpha.9
- https://github.com/Shopify/flash-list/pull/227
## [0.3.3] - 2022-03-16
- Prevent implicit scroll to top on device orientation change
- Change recyclerlistview's bounded size exception to a warning
- https://github.com/Shopify/flash-list/pull/187
## [0.3.2] - 2022-03-15
- Minor changes
## [0.3.1] - 2022-03-15
- Revert react-native-safe-area upgrade and minSdkVersion bump
- https://github.com/Shopify/flash-list/pull/184
## [0.3.0] - 2022-03-15
- Fixed untranspiled library code by enforcing stricter TS rules.
- https://github.com/Shopify/flash-list/pull/181
## [0.2.4] - 2022-03-14
- Added `onLoad` event that is called once the list has rendered items. This is required because FlashList doesn't render items in the first cycle.
- https://github.com/Shopify/flash-list/pull/180
## [0.2.3] - 2022-03-10
- Fixing publish steps for transpiled code
- https://github.com/Shopify/flash-list/pull/150
## [0.2.2] - 2022-03-10
- Fixing publish steps for transpiled code
- https://github.com/Shopify/flash-list/pull/149
## [0.2.1] - 2022-03-09
- Bug fix for style and last separator
- https://github.com/Shopify/flash-list/pull/141
## [0.2.0] - 2022-03-08
- Rename the component from `RecyclerFlatList` to `FlashList`
- https://github.com/Shopify/flash-list/pull/140
## [0.1.0] - 2022-03-02
- Initial release
---
### CLAUDE (CLAUDE.md)
# flash-list
High-performance replacement for React Native's FlatList, built on recycling principles. Used by thousands of React Native apps for smooth scrolling with large datasets.
## Source Layout
- `src/` — library source code (TypeScript)
- `fixture/react-native/` — React Native test/demo app
- `dist/` — compiled output (NOT auto-rebuilt on branch switch)
## Key Constraint
`estimatedItemSize` does **NOT** exist as a prop on FlashList. Do not use it, suggest it, or add it.
## Build & Test
```bash
yarn build # compile TypeScript to dist/
yarn test --forceExit
yarn type-check
yarn lint
```
**Important:** `dist/` is NOT rebuilt on branch switch. Always run `yarn build` after checking out a different branch.
## Node Version
Node 22.18.0
## Commit & PR Conventions
- Use conventional commits: `fix(scope): description`, `feat(scope): description`
- Scope examples: `layout`, `hooks`, `scroll`, `sticky-headers`, `recycling`, `viewability`
- Do NOT include any AI attribution (no `Co-Authored-By` AI lines, no "Generated by" or "Assisted by" text)
- PR titles should be under 70 characters and match the commit format
- Link issues with `Fixes #` in the commit/PR body
## Agent PR Token (MANDATORY)
**All agent-raised PRs MUST use `SHOPIFY_GH_ACCESS_TOKEN` for PR creation.** PRs created without this token cannot be merged.
- On CI: The token is available as `$AGENT_PR_TOKEN` environment variable in agent workflows.
- When creating a PR, **always** use: `GH_TOKEN="$AGENT_PR_TOKEN" gh pr create ...`
- The default `GITHUB_TOKEN` is used for all other operations (issue comments, push, checkout). Only PR creation uses `AGENT_PR_TOKEN`.
- **Never** use bare `gh pr create` without the `GH_TOKEN="$AGENT_PR_TOKEN"` prefix — the PR will be unmergeable.
## CI Cleanup (MANDATORY)
**Before finishing on CI, kill all background processes you started** — especially Metro (`port 8081`). Leftover processes prevent the GitHub Actions job from exiting, causing it to run until timeout and waste CI minutes.
```bash
lsof -ti:8081 | xargs kill -9 2>/dev/null || true
```
## Available Skills
Skills are reusable workflows in `.claude/skills/`. Use them when relevant:
| Skill | When to use |
|---|---|
| `fix-github-issue` | Fixing a bug end-to-end: reproduce, diagnose, fix, verify on simulator, PR |
| `raise-pr` | Creating a PR with proper conventional commit format and no AI attribution |
| `review-and-test` | Reviewing a PR or branch: unit tests, type-check, lint, device testing, RTL/LTR |
| `triage-issue` | Classifying an issue (P0/P1/P2), finding duplicates, applying labels |
| `agent-device` | Interacting with iOS simulator or Android emulator (snapshot-based coordinates) |
| `upgrade-react-native` | Upgrading the fixture app's React Native version |
| `analyze-feedback` | Scanning agent workflow feedback artifacts and incorporating learnings into skills |
## Self-Learning
When you discover something important during a session — a pitfall, a non-obvious behavior, a debugging technique, or a pattern that would save time in the future — update the relevant skill file or this file so the knowledge persists.
**What to capture:**
- New pitfalls or edge cases discovered while fixing bugs
- Debugging techniques that worked (or didn't)
- Corrections to existing instructions that turned out to be wrong
- Non-obvious behaviors of FlashList, React Native, or the build system
**Where to put it:**
- Bug/fix patterns → `fix-github-issue` skill (Common Pitfalls section)
- Testing edge cases → `review-and-test` skill (Edge Cases section)
- Device interaction quirks → `agent-device` skill
- Project-wide facts → this file (`CLAUDE.md`)
- Batch analysis of agent feedback → use the `analyze-feedback` skill to scan workflow artifacts
**How:**
- Include the update in the same PR as the fix (not a separate PR)
- Keep entries concise — one line per pitfall, with enough context to be useful
- On CI, only add critical learnings (things that would cause repeated failures). Minor improvements should wait for interactive sessions.
---
### CODE OF CONDUCT (CODE_OF_CONDUCT.md)
# Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at `opensource@shopify.com`. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
---
### CONTRIBUTING (CONTRIBUTING.md)
# Contributing
We want this community to be **friendly and respectful** to each other. Please follow our [Code of Conduct](./CODE_OF_CONDUCT.md) in all your interactions with the project.
## Development workflow
To get started with the project, run `yarn up` in the root directory to install the required dependencies for `flash-list` and our fixture app:
```sh
yarn up
```
> This project uses [`yarn`](https://classic.yarnpkg.com/) as a package manager. While it's possible to run individual commands with [`npm`](https://github.com/npm/cli), please refrain from using it, especially `npm install`. 🙅
While developing, you can run the [fixture app](/fixture/) to test your changes. To see your changes done in `flash-list` inside the example app, we recommend to open a terminal and run the following command:
```sh
yarn build --watch
```
This way, `flash-list` gets rebuilt on any Javascript/Typescript change in its codebase.
If you change any native code, then you'll need to rebuild the example app.
To start the packager:
```sh
yarn start
```
To run the fixture app on Android:
```sh
yarn run-android
```
To run the example app on iOS:
```sh
yarn run-ios
```
To run the web sample:
```sh
yarn run-web
```
To fix possible formatting errors, run the following:
```sh
yarn lint --fix
```
Formatting errors will also be automatically fixed if you use Visual Studio Code IDE with the [recommended plugins](.vscode/extensions.json)
Remember to add tests for your change if possible. Run the unit tests by:
```sh
yarn test
```
We also have an e2e screenshot test suite built on top of [Detox](https://github.com/wix/Detox/). You can find the e2e tests [here](https://github.com/Shopify/flash-list/tree/main/fixture/e2e). You can run them with:
```sh
run-e2e-ios
# or on android with
run-e2e-android
```
Usually, the screenshots should not change. However, if you do expect change in the UI, you will need to remove the appropriate image in [this](https://github.com/Shopify/flash-list/tree/main/fixture/e2e/artifacts/ios) folder and re-run the tests again.
### Working with documentation website
The repo contains a documentation website build with [Docusaurus](https://docusaurus.io/). Please make sure that your changes are reflected in the documentation, if it's API or configuration changes. Any improvements to documentation itself are also welcomed.
Source files for documentation can be found in [./documentation/docs](./documentation/docs) folder.
To start working with documentation and run it locally:
1. `cd documentation && yarn`
2. `yarn start`
Now local website is running at http://localhost:3000
### Linting and tests
[ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) and [@shopify/eslint-plugin](https://www.npmjs.com/package/@shopify/eslint-plugin) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing.
### Submitting pull requests
Please take some time to correctly fill our [pull request template](.github/PULL_REQUEST_TEMPLATE.md) and detail the proposed changes. This will help reviewers to better understand the context of your PR and provide valuable insights.
When you're sending a pull request:
- Prefer small pull requests focused on one change.
- Update the documentation if your PR changes the API.
- Follow the pull request template when opening a pull request.
- If your PR is a new feature and not a bug fix, consider opening an issue describing your idea. This ensures you get feedback from the maintainers and don't write code that might not be used.
### Releasing a new version
Releases **are done by Shopify engineers** following the steps on [RELEASE.md](./RELEASE.md).
---
### Dev.Yml (dev.yml)
name: FlashList
up:
- ruby
- bundler
- node:
version: 20.11.0
yarn: 1.22.17
packages:
- ./
- yarn_install
---
### LICENSE (LICENSE.md)
Copyright 2022-present, Shopify Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
### Package.Json (package.json)
{
"name": "@shopify/flash-list",
"version": "2.3.2",
"keywords": [
"react-native",
"recyclerview",
"listview",
"flatlist-alternative",
"flatlist",
"recyclerlistview",
"60fps",
"cross-platform",
"react-native-web",
"performance"
],
"publishConfig": {
"access": "public",
"@shopify:registry": "https://registry.npmjs.org/"
},
"repository": {
"type": "git",
"url": "https://github.com/Shopify/flash-list"
},
"description": "FlashList is a more performant FlatList replacement",
"author": "shopify",
"license": "MIT",
"homepage": "https://shopify.github.io/flash-list/",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"up": "yarn && yarn fixture:rn:up && yarn e2e:up && yarn build",
"start": "cd fixture/react-native && react-native start",
"test": "jest",
"lint": "yarn eslint . --ext .ts,.tsx",
"lint:fix": "yarn eslint . --ext .ts,.tsx --fix",
"type-check": "yarn tsc --pretty --noEmit",
"build": "tsc -b",
"watch-web": "sh fixture/web/scripts/copy-dist.sh",
"prepublishOnly": "yarn run build",
"fixture:rn:up": "cd fixture/react-native && yarn && cd ios && bundle exec pod install && cd ../../../",
"fixture:expo:up": "cd fixture/expo && yarn && cd ../../",
"fixture:rn:android": "cd fixture/react-native && yarn react-native run-android && yarn build --watch",
"fixture:rn:ios": "cd fixture/react-native && yarn react-native run-ios && yarn build --watch",
"ra": "yarn fixture:rn:android",
"ri": "yarn fixture:rn:ios",
"fixture:expo:start": "cd fixture/expo && yarn start",
"rw": "yarn fixture:expo:start",
"e2e:up": "cd fixture/react-native/ios && brew tap wix/brew && brew trust --formula wix/brew/applesimutils && brew install applesimutils && cd ../../../",
"e2e:ios": "cd fixture/react-native && yarn e2e:build:ios && yarn e2e:test:ios",
"rei": "yarn run e2e:ios",
"e2e:android": "cd fixture/react-native && yarn e2e:build:android && yarn e2e:test:android",
"rea": "yarn run-e2e-android",
"predeploy:website": "sh ./build_website.sh",
"deploy:website": "gh-pages -d website/_site"
},
"peerDependencies": {
"@babel/runtime": "*",
"react": "*",
"react-native": "*"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/runtime": "^7.25.0",
"@quilted/react-testing": "0.5.27",
"@react-native-community/eslint-config": "^3.0.3",
"@shopify/eslint-plugin": "^41.3.1",
"@types/jest": "^29.5.10",
"@types/react": "^18.0.24",
"babel-jest": "^28.1.1",
"enhanced-resolve": "^5.9.3",
"eslint": "8.57.1",
"gh-pages": "^6.2.0",
"jest": "^29.7.0",
"metro-react-native-babel-preset": "^0.77.0",
"prettier": "^2.7.1",
"react": "18.3.1",
"react-native": "0.76.5",
"react-test-renderer": "18.2.0",
"typescript": "5.8.3"
},
"files": [
"dist",
"src",
"jestSetup.js",
"!__tests__"
]
}
---
### RELEASE (RELEASE.md)
# Release
1. Before releasing a new version of the package, make sure you are pointing to the latest commit from `main`.
2. Run `yarn version` and follow the prompts to choose a version for the package.
3. Push the changes and new tags to GitHub with `git push origin main --follow-tags`.
4. Publish the release on [Github Releases](https://github.com/Shopify/flash-list/releases) and the package should be automatically published by Github Actions.
---
### Shipit.Production.Yml (shipit.production.yml)
dependencies:
override:
- yarn install
deploy:
pre:
- yarn run build
---
### Tsconfig.Json (tsconfig.json)
{
"extends": "./shared/tsconfig.base.json",
"compilerOptions": {
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo",
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "src/__tests__", "**/*.test.ts?(x)"]
}
---
### Documentation/Dev.Yml (documentation/dev.yml)
name: flash-list
up:
- node:
version: v20.4.0
yarn: 1.22.15
server:
desc: Run docusaurus documentation local server
run: yarn run start --no-open
build:
desc: build and serve the static website
run: |
yarn run build
yarn run serve
open:
app: http://localhost:3000
---
### Documentation/Microsite.Yml (documentation/microsite.yml)
# Please do not remove this file as it is required to populate the `catalog.docs.shopify.io` page
title: FlashList
# Modify 'description'
description: >
FlashList is a more performant replacement of the FlatList component.
repository:
home: https://github.com/Shopify/flash-list
branch: main
blob_url: "%home%/blob/%branch%/%path%"
---
### Documentation/Package.Json (documentation/package.json)
{
"name": "flash-list",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^3.7.0",
"@docusaurus/preset-classic": "^3.7.0",
"@easyops-cn/docusaurus-search-local": "^0.49.2",
"@mdx-js/react": "^3.1.0",
"@svgr/webpack": "^5.5.0",
"clsx": "^1.1.1",
"file-loader": "^6.2.0",
"prism-react-renderer": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"url-loader": "^4.1.1"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
---
### Documentation/Docs/Index (documentation/docs/index.mdx)
---
id: introduction
title: Introduction 📚
slug: /
sidebar_position: 0
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
**Fast & performant React Native list. No more blank cells.**
Swap from FlatList in seconds. Get instant performance. Build for RN's new architecture.
## Installation
Add the package to your project via:
```bash
yarn add @shopify/flash-list
```
- ✅ This library can be used with "Expo Go" from SDK 46 upwards or with a development client
- ✅ You can use this library with [Development Builds](https://docs.expo.dev/development/introduction/). No config plugin is required.
```bash
npx expo install @shopify/flash-list expo-dev-client
```
You can then [create a new Development Build](https://docs.expo.dev/development/build/) and begin using `FlashList`.
### Usage
Read more about the usage of `FlashList` [here](/usage)
### Old architecture / FlashList v1
If you're running on old architecture or using an older version of FlashList, you can access the documentation specific to that version by selecting it from the version dropdown in the navigation bar, or using these direct links:
- [Version 1.x documentation](/flash-list/docs/1.x/)
### App / Playground
The [fixture](https://github.com/Shopify/flash-list/tree/main/fixture) is an example app showing how to use the library.
---
### Documentation/Docs/Known Issues (documentation/docs/known-issues.md)
---
id: known-issues
title: Known issues
slug: /known-issues
sidebar_position: 3
---
# Known Issues
FlashList and FlatList have very different internals. While the API is almost the same, the behaviour might be different in some cases due to a bug, limitation or missing implementation. In any case, if the issue is already listed here don't create a new one.
### 1) Horizontal lists + RTL Layout
We have a limitation where we're not able to read the padding applied on the list using `contentContainerStyle`. Small values shouldn't cause an issue; however, if you require precise `scrollTo` or `initialScrollIndex`, then apply padding or margin to the header instead. Please note that this applies only to RTL language layouts.
### 2) Horizontal Lists with headers
If the horizontal list has a fixed size or header, we assume that the height of the list is fixed. If your use case requires the list to match the size of the items or resize based on tallest child, just skip using the header. You can just render the header as the first item in the list and give it a separate type using `getItemType`.
### 3) Data re-ordering can cause items to move
This is because `maintainVisibleContentPosition` is enabled by default. If you're running into this then you can disable it by setting `maintainVisibleContentPosition={{disabled:true}}`. Having it enabled by default allows us to handle any layout changes while scrolling upwards in a better way like after an orientation change or a large scroll jump to the last item.
---
### Documentation/Docs/V2 Changes (documentation/docs/v2-changes.md)
---
id: v2-changes
title: What's new in v2
slug: /v2-changes
sidebar_position: 4
---
## New props
- `masonry`: Enable masonry layout for grid-like interfaces with varying item heights.
```jsx
}
/>
```
- `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
loadOlderMessages()}
onStartReachedThreshold={0.1}
renderItem={({ 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.
- `animateAutoScrollToBottom`: Scroll with animation whenever `autoscrollToBottom` is triggered. Default is `true`.
- `startRenderingFromBottom`: If true, initial render will start from the bottom, useful for chat interfaces.
```jsx
}
/>
```
- `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.
## 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
{
layout.span = item.span; // Set span
}}
renderItem={({ 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.
## New hooks
### 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 (
setIsExpanded(!isExpanded)}>
{item.title}
);
};
```
### 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 (
setIsExpanded(!isExpanded)}>
{item.title}
);
};
```
### 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 (
}
/>
);
};
// When mapping over items inside components:
const NestedList = ({ items }) => {
const { getMappingKey } = useMappingHelper();
return (
{items.map((item, index) => (
{item.title}
))}
);
};
```
### useFlashListContext
Exposes helpers to easily access `ref` of FlashList. It also exposes `ref` of ScrollView. Ideal for use within child components or `CellRendererComponent`.
## New helper components
### LayoutCommitObserver
A component that observes when FlashList commits a layout. This is particularly useful when you have one or more FlashLists somewhere down the component tree and need to know when all of them have finished their layout commit.
Read more [here.](./fundamentals/layout-commit-observer.md)
## Deprecated
- `estimatedItemSize`: No longer used.
- `estimatedListSize`: No longer used.
- `estimatedFirstItemOffset`: No longer used.
- `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.
---
### Documentation/Docs/V2 Migration (documentation/docs/v2-migration.md)
---
id: v2-migration
title: Migrating to v2
slug: /v2-migration
sidebar_position: 5
---
# Migrating from FlashList v1 to v2
This guide will help you migrate your existing FlashList v1 implementation to v2. FlashList v2 brings significant improvements in performance, developer experience, and new features while removing some deprecated props.
## Overview of Major Changes
- **New architecture is required** - v2 only works on top of React Native's new architecture
- **No more size estimates required** - FlashList v2 automatically handles all sizing
- **Masonry layout is now a prop** - MasonryFlashList component is deprecated
- **maintainVisibleContentPosition enabled by default** - Better scroll position handling
## Step-by-Step Migration
### Step 1: Update Package Version
```bash
npm install @shopify/flash-list@^2.0.0
# or
yarn add @shopify/flash-list@^2.0.0
```
### Step 2: Remove Deprecated Props
The following props have been deprecated and should be removed from your FlashList components:
#### Size Estimation Props (No Longer Needed!)
```diff
```
#### Other Deprecated Props
```diff
```
### Step 3: Update Changed Props
#### overrideItemLayout
In v1, `overrideItemLayout` allowed both span changes and size estimates. In v2, it only supports span changes:
```diff
// v1
overrideItemLayout={(layout, item, index) => {
layout.span = item.span;
- layout.size = 100; // No longer supported
}}
// v2
overrideItemLayout={(layout, item) => {
layout.span = item.span; // Only span is supported
}}
```
### Step 4: Migrate MasonryFlashList to FlashList with masonry prop
```diff
// v1
- import { MasonryFlashList } from "@shopify/flash-list";
-
// v2
+ import { FlashList } from "@shopify/flash-list";
+
```
Note: `getColumnFlex` from MasonryFlashList is not supported in v2.
### Step 5: Update Ref Types
The ref type for FlashList has changed from `FlashList` to `FlashListRef`:
```diff
// v1
- import { FlashList } from "@shopify/flash-list";
- const listRef = useRef>(null);
// v2
+ import { FlashList, FlashListRef } from "@shopify/flash-list";
+ const listRef = useRef>(null);
```
### Step 6: Replace CellContainer with View
`CellContainer` is no longer exported in v2. Replace it with React Native's `View`. Apps forwarding custom `CellRendererComponent` might need this change.
---
### Documentation/Docs/Fundamentals/Layout Commit Observer (documentation/docs/fundamentals/layout-commit-observer.md)
---
id: layout-commit-observer
title: Layout Commit Observer
---
# Layout Commit Observer
The `LayoutCommitObserver` is a utility component that helps you track when all FlashList components in your component tree have completed their layout. This is particularly useful for coordinating complex UI behaviors that depend on list rendering completion. Doing your own `setState` in this callback will block paint till your state change is ready to be committed.
## Overview
When working with multiple FlashList components or when you need to perform actions after a FlashList has finished its render, the LayoutCommitObserver provides a clean way to observe and react to these layout events. Please note that the callback is fired after every layout operation and not just the first one.
## When to Use
- Measure size of views after all internal lists have rendered
- Don't have access to FlashList for example, your component just accepts `children` prop.
## When not to use
- If you don't need to block paint then using the `onLoad` callback is a better approach.
- If you only have one FlashList and have access to it. `onCommitLayoutEffect` is a prop on FlashList too.
## Basic Usage
Wrap your component tree containing FlashLists with LayoutCommitObserver:
```tsx
import { LayoutCommitObserver } from "@shopify/flash-list";
function MyScreen() {
const handleLayoutComplete = () => {
console.log("All FlashLists have completed their initial layout!");
// Perform any post-layout actions here
};
return (
);
}
```
---
### Documentation/Docs/Fundamentals/Performant Components (documentation/docs/fundamentals/performant-components.md)
---
id: performance
title: Performance
---
## Profiling
:::warning
Before assessing your list's performance, make sure you are in release mode. On Android, you can disable JS dev mode inside the developer menu, whereas you need to run the release configuration on iOS.
FlashList can appear to be slower than FlatList in dev mode. The primary reason is a much smaller and fixed [window size](https://reactnative.dev/docs/virtualizedlist#windowsize) equivalent. Click [here](https://reactnative.dev/docs/performance#running-in-development-mode-devtrue) to know more about why you shouldn't profile with dev mode on.
:::
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.
# Writing Performant Components
While `FlashList` does its best to achieve high performance, it will still perform poorly if your item components are slow to render. In this post, let's dive deeper into how you can remedy this.
## 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. When optimizing your item component, try to ensure as few things as possible have to be re-rendered and recomputed when recycling.
## Optimizations
There's lots of optimizations that are applicable for _any_ React Native component and which might help render times of your item components as well. Usage of `useCallback`, `useMemo`, and `useRef` is advised - but don't use these blindly, always measure the performance before and after making your changes.
:::note
Always profile performance in the release mode. `FlashList`'s performance between JS dev and release mode differs greatly.
:::
### Remove `key` prop
:::warning
Using `key` prop inside your item and item's nested components will highly degrade performance.
:::
Make sure your **item components and their nested components don't have a `key` prop**. Using this prop will lead to `FlashList` not being able to recycle views, losing all the benefits of using it over `FlatList`.
#### Why are keys harmful to FlashList?
FlashList's core performance advantage comes from **recycling** components instead of creating and destroying them however, when you add a `key` prop that changes between different data items, React treats the component as entirely different and forces a complete re-creation of the component tree.
For example, if we had a following item component:
```tsx
const MyNestedComponent = ({ item }) => {
return I am nested!;
};
const MyItem = ({ item }) => {
return (
{item.title}
);
};
```
Then the `key` prop should be removed from both `MyItem` and `MyNestedComponent`. It isn't needed and react can alredy take care of updating the components.
```tsx
const MyNestedComponent = ({ item }) => {
return I am nested!;
};
const MyItem = ({ item }) => {
return (
{item.title}
);
};
```
There might be cases where React forces you to use `key` prop, such as when using `map`. In such circumstances, **use `useMappingHelper`** to ensure optimal performance:
```tsx
import { useMappingHelper } from "@shopify/flash-list";
const MyItem = ({ item }) => {
const { getMappingKey } = useMappingHelper();
return (
<>
{item.users.map((user, index) => (
{user.name}
))}
>
);
};
```
The `useMappingHelper` hook intelligently provides the right key strategy:
- **When inside FlashList**: Uses stable keys that don't change during recycling
- **When outside FlashList**: Uses the provided item key for proper React reconciliation
This approach ensures that:
- Components can be recycled properly within FlashList
- React's reconciliation works correctly
- Performance remains optimal
:::info
`useMappingHelper` should be used whenever you need to map over arrays inside FlashList item components. It automatically handles the complexity of providing recycling-friendly keys.
:::
### Difficult calculations
If you do any calculations that might take a lot of resources, consider memoizing it, making it faster, or removing it altogether. The render method of items should be as efficient as possible:
### `getItemType`
**If you have different types of cell components and these are vastly different, consider leveraging the [`getItemType` prop](/usage#getitemtype)**. For example, if we were building a messages list, we could write it like this:
```tsx
// A message can be either a text or an image
enum MessageType {
Text,
Image,
}
interface TextMessage {
text: string;
type: MessageType.Text;
}
interface ImageMessage {
image: ImageSourcePropType;
type: MessageType.Image;
}
type Message = ImageMessage | TextMessage;
const MessageItem = ({ item }: { item: Message }) => {
switch (item.type) {
case MessageType.Text:
return {item.text};
case MessageType.Image:
return ;
}
};
// Rendering the actual messages list
const MessageList = () => {
return ;
};
```
However, this implementation has one performance drawback. When the list recycles items and the `MessageType` changes from `Text` to `Image` or vice versa, React won't be able to optimize the re-render since the whole render tree of the item component changes. We can fix this by changing the `MessageList` to this:
```tsx
const MessageList = () => {
return (
{
return item.type;
}}
/>
);
};
```
`FlashList` will now use separate recycling pools based on `item.type`. That means we will never recycle items of different types, making the re-render faster.
### Leaf components
Let's consider the following example:
```tsx
const MyHeavyComponent = () => {
return ...;
};
const MyItem = ({ item }) => {
return (
<>
{item.title}
>
);
};
```
Since `MyHeavyComponent` does not directly depend on the `item` prop, `memo` can be used to skip re-rending `MyHeavyComponent` when the item is recycled and thus re-rendered:
```tsx
const MyHeavyComponent = () => {
return ...;
};
const MemoizedMyHeavyComponent = memo(MyHeavyComponent);
const MyItem = ({ item }: { item: any }) => {
return (
<>
{item.title}
>
);
};
```
---
### Documentation/Docs/Fundamentals/Recycling (documentation/docs/fundamentals/recycling.md)
---
id: recycling
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.
FlashList now comes with `useRecyclingState` hook that can reset the state automatically without an additional render.
```tsx
const MyItem = ({ item }) => {
// 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 (
setLiked(true)}>
{liked}
);
};
```
When optimizing your item component, try to ensure as few things as possible have to be re-rendered and recomputed when recycling.
---
### Documentation/Docs/Fundamentals/Usage (documentation/docs/fundamentals/usage.md)
---
id: usage
title: Usage
slug: /usage
sidebar_position: 0
---
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.
```tsx
import React from "react";
import { View, Text, StatusBar } from "react-native";
import { FlashList } from "@shopify/flash-list";
const DATA = [
{
title: "First Item",
},
{
title: "Second Item",
},
];
const MyList = () => {
return (
{item.title}}
/>
);
};
```
## Important things to know {#migration-steps}
To avoid common pitfalls, you can also follow these steps for migrating from `FlatList`, based on our own experience.
1. Simply change from `FlatList` to `FlashList` and render the list.
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 our hook called [`useMappingHelper`](https://shopify.github.io/flash-list/docs/usage/#usemappinghelper).
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.
6. 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.
7. `keyExtractor` is important to prevent glitches due to item layout changes when going upwards. We highly recommend having a valid `keyExtractor` with v2.
8. Read about new hooks that simplify recycling and reacting to layout changes: [`useLayoutState`](https://shopify.github.io/flash-list/docs/usage/#usemappinghelper), [`useRecyclingState`](https://shopify.github.io/flash-list/docs/usage/#usemappinghelper)
9. 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.
# Props
### **`renderItem`**
:::note
Required
:::
```tsx
renderItem: ({ item, index, target, extraData }) => void;
```
Takes an item from `data` and renders it into the list. Typical usage:
```tsx
renderItem = ({item}) => (
{item.title}
);
...
```
Provides additional metadata like `index`
- `item` (`Object`): The item from `data` being rendered.
- `index` (`number`): The index corresponding to this item in the `data` array.
- `target` (`string`): FlashList may render your items for multiple reasons.
- `Cell` - This is for your list item.
- `Measurement` - Might be invoked for size measurement and won't be visible. You can ignore this in analytics.
- `StickyHeader` - This is for your sticky header. Use this to change your item's appearance while it's being used as a sticky header.
- `extraData` (`Object`) - This is the same `extraData` prop that was passed to `FlashList`.
### **`data`**
:::note
Required
:::
For simplicity, data is a plain array of items of a given type.
```tsx
data: ItemT[];
```
### `CellRendererComponent`
Each cell is rendered using this element. Can be a React Component Class, or a render function. The root component should always be a `CellContainer` which is also the default component used. Ensure that the original `props` are passed to the returned `CellContainer`. The `props` contain the following properties:
- `onLayout`: Method for updating data about the real `CellContainer` layout
- `index`: Index of the cell in the list, you can use this to query data if needed
- `style`: Style of `CellContainer`, including:
- `flexDirection`: Depends on whether your list is horizontal or vertical
- `position`: Value of this will be `absolute` as that's how `FlashList` positions elements
- `left`: Determines position of the element on x axis
- `top`: Determines position of the element on y axis
- `width`: Determines width of the element (present when list is vertical)
- `height`: Determines height of the element (present when list is horizontal)
When using with `react-native-reanimated`, you can wrap `CellContainer` in `Animated.createAnimatedComponent` (this is similar to using `Animated.View`):
```ts
const AnimatedCellContainer = Animated.createAnimatedComponent(CellContainer);
return (
{
return (
);
}}
/>
);
```
```ts
CellRendererComponent?: React.ComponentType | undefined;
```
### `ItemSeparatorComponent`
Rendered in between each item, but not at the top or bottom. By default, `leadingItem` and `trailingItem` (if available) props are provided.
```tsx
ItemSeparatorComponent?: React.ComponentType;
```
### `ListEmptyComponent`
Rendered when the list is empty. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``).
```tsx
ListEmptyComponent?: React.ComponentType | React.ReactElement>;
```
### `ListEmptyComponentStyle`
Styling for internal View for `ListEmptyComponent`.
```tsx
ListEmptyComponentStyle?: StyleProp;
```
### `ListFooterComponent`
Rendered at the bottom of all the items. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``).
```tsx
ListFooterComponent?: React.ComponentType | React.ReactElement>;
```
### `ListFooterComponentStyle`
Styling for internal View for `ListFooterComponent`.
```tsx
ListFooterComponentStyle?: StyleProp;
```
### `ListHeaderComponent`
Rendered at the top of all the items. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``).
```tsx
ListHeaderComponent?: React.ComponentType | React.ReactElement>;
```
### `ListHeaderComponentStyle`
Styling for internal View for `ListHeaderComponent`.
```tsx
ListHeaderComponentStyle?: StyleProp;
```
### `contentContainerStyle`
```tsx
contentContainerStyle?: ContentStyle;
export type ContentStyle = Pick<
ViewStyle,
| "backgroundColor"
| "paddingTop"
| "paddingLeft"
| "paddingRight"
| "paddingBottom"
| "padding"
| "paddingVertical"
| "paddingHorizontal"
>;
```
You can use `contentContainerStyle` to apply padding that will be applied to the whole content itself. For example, you can apply this padding, so that all of your items have leading and trailing space.
### `drawDistance`
```tsx
drawDistance?: number;
```
Draw distance for advanced rendering (in `dp`/`px`).
### `extraData`
A marker property for telling the list to re-render (since it implements `PureComponent`). If any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the `data` prop, stick it here and treat it immutably.
```tsx
extraData?: any;
```
### `horizontal`
If `true`, renders items next to each other horizontally instead of stacked vertically. Default is `false`.
```tsx
horizontal?: boolean;
```
### `inverted`
Reverses the direction of the list. Uses CSS transforms (`scaleY(-1)` on iOS/web, `rotate(180deg)` on Android) to flip the list and its contents.
Useful for chat-like interfaces where the newest content appears at the bottom.
:::note
On Android, a rotate transform is used instead of scale for performance reasons. This causes the scrollbar to appear on the left side of the list.
:::
```tsx
inverted?: boolean;
```
### `initialScrollIndex`
Instead of starting at the top with the first item, start at `initialScrollIndex`.
```tsx
initialScrollIndex?: number;
```
### `initialScrollIndexParams`
Additional configuration for initialScrollIndex. Use viewOffset to apply an offset to the initial scroll position as defined by initialScrollIndex.
Ignored if initialScrollIndex is not set.
```tsx
initialScrollIndexParams?: { viewOffset?: number };
```
### `keyExtractor`
```tsx
keyExtractor?: (item: object, index: number) => string;
```
Used to extract a unique key for a given item at the specified index. Key is used for optimizing performance. Defining `keyExtractor` is also necessary when doing [layout animations](/guides/layout-animation) to uniquely identify animated components.
### `maintainVisibleContentPosition`
```tsx
maintainVisibleContentPosition?: {
disabled?: boolean;
autoscrollToTopThreshold?: number;
autoscrollToBottomThreshold?: number;
startRenderingFromBottom?: boolean;
};
```
Configuration for maintaining scroll position when content changes. This is enabled by default to reduce visible glitches.
- `disabled`: Set to true to disable this feature. It's 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.
- `animateAutoScrollToBottom`: Scroll with animation whenever `autoScrollToBottom` is triggered. Default is `true`.
- `startRenderingFromBottom`: If true, initial render will start from the bottom, useful for chat interfaces.
Example:
```jsx
}
/>
```
### `masonry`
```tsx
masonry?: boolean;
```
Enable masonry layout for grid-like interfaces with varying item heights. When used with `numColumns > 1`, this creates a masonry-style layout.
```jsx
}
/>
```
### `maxItemsInRecyclePool`
Maximum number of items in the recycle pool. These are the items that are cached in the recycle pool when they are scrolled off the screen. Unless you have a huge number of item types, you shouldn't need to set this.
Setting this to 0, will disable the recycle pool and items will unmount once they are scrolled off the screen.
There's no limit by default.
### `numColumns`
Multiple columns can only be rendered with `horizontal={false}` and will zig-zag like a `flexWrap` layout. Items should all be the same height - masonry layouts are not supported.
`numColumns?: number;`
### `stickyHeaderConfig`
Configuration object for sticky header behavior and appearance. All properties are optional.
```tsx
stickyHeaderConfig?: {
useNativeDriver?: boolean;
offset?: number;
backdropComponent?: React.ComponentType | React.ReactElement | null;
zIndex?: number;
hideRelatedCell?: boolean;
};
```
#### `useNativeDriver`
If true, the sticky headers will use native driver for animations. Default is `true`.
```tsx
useNativeDriver?: boolean;
```
#### `offset`
Offset from the top of the list where sticky headers should stick.
This is useful when you have a fixed header or navigation bar at the top of your screen
and want sticky headers to appear below it instead of at the very top.
Default is `0`.
```tsx
offset?: number;
```
#### `backdropComponent`
Component to render behind sticky headers (e.g., a backdrop or blur effect).
Renders in front of the scroll view content but behind the sticky header itself.
Useful for creating visual separation or effects like backgrounds with blur.
```tsx
backdropComponent?: React.ComponentType | React.ReactElement | null;
```
#### `zIndex`
zIndex applied to the sticky header container. Default is `2`.
```tsx
zIndex?: number;
```
#### `hideRelatedCell`
When a sticky header is displayed, the cell associated with it is hidden.
Default is `false`.
```tsx
hideRelatedCell?: boolean;
```
**Example:**
```jsx
,
zIndex: 3,
hideRelatedCell: true,
}}
renderItem={({ item }) => }
/>
```
### `onChangeStickyIndex`
Callback invoked when the currently displayed sticky header changes as you scroll.
Receives the current sticky header index and the previous sticky header index.
This is useful for tracking which header is currently stuck at the top while scrolling.
The index refers to the position of the item in your data array that's being used as a sticky header.
```tsx
onChangeStickyIndex?: (current: number, previous: number) => void;
```
Example:
```jsx
{
console.log(`Sticky header changed from ${previous} to ${current}`);
}}
renderItem={({ item }) => }
/>
```
### `onBlankArea`
```tsx
onBlankArea?: (blankAreaEvent: {
offsetStart: number;
offsetEnd: number;
blankArea: number;
}) => void;
```
`FlashList` computes blank space that is visible to the user during scrolling or the initial loading of the list.
Values reported:
- `offsetStart`: visible blank space on top of the screen (while going up). If value is greater than 0, it's visible to user.
- `offsetEnd`: visible blank space at the end of the screen (while going down). If value is greater than 0, it's visible to user.
- `blankArea`: maximum of `offsetStart` and `offsetEnd`. You might see negative values indicating that items are rendered outside the list's visible area.
:::warning
This callback will be triggered even if the blanks are excepted - for example, when the list does not have enough items to fill the screen.
:::
:::note
This event isn't synced with `onScroll` event from the JS layer but works with native methods `onDraw` (Android) and `layoutSubviews` (iOS).
:::
### `onCommitLayoutEffect`
```tsx
onCommitLayoutEffect?: () => void;
```
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.
### `onEndReached`
```tsx
onEndReached?: () => void;
```
Called once when the scroll position gets within `onEndReachedThreshold` of the rendered content.
### `onEndReachedThreshold`
```tsx
onEndReachedThreshold?: number;
```
How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end of the content to trigger the `onEndReached` callback. Thus a value of 0.5 will trigger `onEndReached` when the end of the content is within half the visible length of the list.
### `onLoad`
```tsx
onLoad: (info: { elapsedTimeInMs: number }) => void;
```
This event is raised once the list has drawn items on the screen. It also reports elapsedTimeInMs which is the time it took to draw the items. This is required because FlashList doesn't render items in the first cycle. Items are drawn after it measures itself at the end of first render. Please note that the event is not fired if ListEmptyComponent is rendered.
### `onRefresh`
```tsx
onRefresh?: () => void;
```
If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the `refreshing` prop correctly.
### `getItemType`
```tsx
getItemType?: (
item: T,
index: number,
extraData?: any
) => string | number | undefined;
```
Allows developers to specify item types. This will improve recycling if you have different types of items in the list. Right type will be used for the right item.Default type is 0. If you don't want to change for an indexes just return undefined. You can see example of how to use this prop [here](performant-components.md#getitemtype).
:::warning Performance
This method is called very frequently. Keep it fast.
:::
### `onStartReached`
```tsx
onStartReached?: () => void;
```
Called once when the scroll position gets within `onStartReachedThreshold` of the start of the content. Useful for loading older content in infinite scroll scenarios like chat applications.
```jsx
loadOlderMessages()}
onStartReachedThreshold={0.1}
renderItem={({ item }) => }
/>
```
### `onStartReachedThreshold`
```tsx
onStartReachedThreshold?: number;
```
How far from the start (in units of visible length of the list) the top edge of the list must be from the start of the content to trigger the `onStartReached` callback. Value of 0.5 will trigger `onStartReached` when the start of the content is within half the visible length of the list from the top.
### `onViewableItemsChanged`
```tsx
interface ViewToken {
index: number;
isViewable: boolean;
item: string;
key: string;
timestamp: number;
}
onViewableItemsChanged?: ((info: {
viewableItems: ViewToken[];
changed: ViewToken[];
}) => void) | null | undefined
```
Called when the viewability of rows changes, as defined by the `viewabilityConfig` prop. Array of `changed` includes `ViewToken`s that both visible and non-visible items. You can use the `isViewable` flag to filter the items.
:::note
If you are tracking the time a view becomes (non-)visible, use the `timestamp` property. We make no guarantees that in the future viewability callbacks will be invoked as soon as they happen - for example, they might be deferred until JS thread is less busy.
:::
### `optimizeItemArrangement`
```tsx
optimizeItemArrangement?: boolean;
```
When enabled with `masonry` layout, this will try to reduce differences in column height by modifying item order. Default is `true`.
### `overrideItemLayout`
```tsx
overrideItemLayout?: (
layout: { span?: number;},
item: T,
index: number,
maxColumns: number,
extraData?: any
) => void;
```
This method can be used to change column span of an item.
In v2, span is supported, but size estimates are no longer needed or read.
Changing item span is useful when you have grid layouts (numColumns > 1) and you want few items to be bigger than the rest.
```jsx
{
layout.span = item.span; // Set span
}}
renderItem={({ item }) => }
/>
```
:::warning Performance
This method is called very frequently. Keep it fast.
:::
### `overrideProps`
```tsx
overrideProps?: object;
```
Allows overriding internal ScrollView props. Props provided here are spread onto the internal ScrollView after all other props, so they take highest priority.
This can be useful for cases where you need to set a style on the ScrollView itself rather than the outer container. For example, to enable visible overflow:
```tsx
```
:::caution
Use with caution — overriding internal props may interfere with FlashList's layout and recycling behavior.
:::
### `progressViewOffset`
```tsx
progressViewOffset?: number;
```
Set this when offset is needed for the loading indicator to show correctly.
### `refreshControl`
```tsx
refreshControl?: React.ReactElement>;
```
A custom refresh control element. When set, it overrides the default `` component built internally. The onRefresh and refreshing props are also ignored. Only works for vertical VirtualizedList.
### `refreshing`
```tsx
refreshing?: boolean;
```
Set this true while waiting for new data from a refresh.
### `renderScrollComponent`
```tsx
import type { ScrollViewProps } from "react-native";
renderScrollComponent?:
| React.ComponentType
| React.FC;
```
Rendered as the main scrollview.
### `style`
```tsx
style?: StyleProp;
```
Style for the FlashList's parent container. It's highly recommended to avoid 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.
### `viewabilityConfig`
```tsx
interface ViewabilityConfig: {
minimumViewTime: number;
viewAreaCoveragePercentThreshold: number;
itemVisiblePercentThreshold: number;
waitForInteraction: boolean;
}
viewabilityConfig?: ViewabilityConfig;
```
`viewabilityConfig` is a default configuration for determining whether items are viewable.
:::warning
Changing viewabilityConfig on the fly is not supported
:::
Example:
```tsx
void)
| null;
}
viewabilityConfigCallbackPairs: ViewabilityConfigCallbackPairs | undefined;
```
List of `ViewabilityConfig`/`onViewableItemsChanged` pairs. A specific `onViewableItemsChanged` will be called when its corresponding `ViewabilityConfig`'s conditions are met.
# Hooks
### useLayoutState
```tsx
const [state, setState] = useLayoutState(initialState);
```
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 (
setIsExpanded(!isExpanded)}>
{item.title}
);
};
```
### useRecyclingState
```tsx
const [state, setState] = useRecyclingState(
initialState,
dependencies,
resetCallback
);
```
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 (
setIsExpanded(!isExpanded)}>
{item.title}
);
};
```
### useMappingHelper
```tsx
const { getMappingKey } = useMappingHelper();
```
Returns a function that helps create a mapping key for items when using `.map()` in your render methods. Using this ensures that performance is optimal for FlashList by providing consistent keys that work with the recycling system.
The `getMappingKey` function takes two parameters:
- `index`: The index of the item in the array
- `itemKey`: A unique identifier for the item (string, number, or bigint)
It returns the appropriate key value to use in the `key` prop based on the current context.
**Basic usage:**
```jsx
import { useMappingHelper } from "@shopify/flash-list";
const MyComponent = ({ items }) => {
const { getMappingKey } = useMappingHelper();
return (
{items.map((item, index) => (
{item.title}
))}
);
};
```
**When to use it:**
- When mapping over arrays to create lists of components inside FlashList items
- When building nested components that render multiple items from an array
- To ensure consistent key generation that works well with FlashList's recycling system
### useFlashListContext
Exposes helpers to easily access `ref` of FlashList. It also exposes `ref` of ScrollView. Ideal for use within child components or `CellRendererComponent`.
# FlashList methods
### `prepareForLayoutAnimationRender()`
```tsx
prepareForLayoutAnimationRender(): void;
```
Run this method before running layout animations, such as when animating an element when deleting it. This method disables recycling for the next frame so that layout animations run well.
:::warning
Avoid using this when making large changes to the data as the list might draw too much to run animations since the method disables recycling temporarily. Single item insertions or deletions should animate smoothly. The render after animation will enable recycling again and you can stop avoiding making large data changes.
:::
### `recordInteraction()`
```tsx
recordInteraction();
```
Tells the list an interaction has occurred, which should trigger viewability calculations, e.g. if `waitForInteractions` is true and the user has not scrolled. You should typically call `recordInteraction()` when user for example taps on an item or invokes a navigation action.
### `recomputeViewableItems()`
```tsx
recomputeViewableItems();
```
Retriggers viewability calculations. Useful to imperatively trigger viewability calculations.
### `scrollToEnd()`
```tsx
scrollToEnd?: (params?: { animated?: boolean | null | undefined });
```
Scrolls to the end of the content.
### `scrollToIndex()`
```tsx
scrollToIndex(params: {
animated?: boolean | null | undefined;
index: number;
viewOffset?: number | undefined;
viewPosition?: number | undefined;
});
```
Scroll to a given index.
### `scrollToItem()`
```tsx
scrollToItem(params: {
animated?: boolean | null | undefined;
item: any;
viewPosition?: number | undefined;
});
```
Scroll to a given item.
### `scrollToOffset()`
```tsx
scrollToOffset(params: {
animated?: boolean | null | undefined;
offset: number;
});
```
Scroll to a specific content pixel offset in the list.
Param `offset` expects the offset to scroll to. In case of `horizontal` is true, the offset is the x-value, in any other case the offset is the y-value.
Param `animated` (`false` by default) defines whether the list should do an animation while scrolling.
### `getVisibleIndices()`
Returns an array of indices that are currently visible in the list.
```tsx
getVisibleIndices(): number[];
```
### `getLayout()`
Returns the current layout information for the list.
```tsx
getLayout(): { x: number, y: number, width: number; height: number };
```
### `flashScrollIndicators()`
Shows the scroll indicators momentarily.
```tsx
flashScrollIndicators(): void;
```
### `getNativeScrollRef()`
Returns a reference to the underlying scroll view.
```tsx
getNativeScrollRef(): React.RefObject;
```
### `getScrollResponder()`
Returns the scroll responder of the underlying scroll view.
```tsx
getScrollResponder(): any;
```
### `getScrollableNode()`
Returns the native scrollable node of the underlying scroll view.
```tsx
getScrollableNode(): any;
```
### `scrollToTop()`
Scrolls to the top of the list.
```tsx
scrollToTop(params?: { animated?: boolean }): void;
```
### `getFirstItemOffset()`
Returns the offset of the first item (useful for calculating header size or top padding).
```tsx
getFirstItemOffset(): number;
```
### `getWindowSize()`
Returns the current rendered dimensions of the list.
```tsx
getWindowSize(): { width: number, height: number };
```
### `getFirstVisibleIndex()`
Returns the index of the first visible item in the list.
```tsx
getFirstVisibleIndex(): number;
```
# ScrollView props
`FlashList`, as `FlatList`, uses `ScrollView` under the hood. You can take a look into the React Native documentation for [`ScrollView`](https://reactnative.dev/docs/scrollview) to see the exhaustive list of props.
# Unsupported `FlatList` props
The following props from `FlatList` are currently not implemented:
- [`columnWrapperStyle`](https://reactnative.dev/docs/flatlist#columnwrapperstyle)
- [`debug`](https://reactnative.dev/docs/virtualizedlist#debug)
- [`listKey`](https://reactnative.dev/docs/virtualizedlist#listkey)
There are also `FlatList` props that would bring no value if ported to `FlashList` due to the differences in their underlying implementation:
- [`disableVirtualization`](https://reactnative.dev/docs/virtualizedlist#disablevirtualization)
- [`getItemLayout`](https://reactnative.dev/docs/flatlist#getItemLayout)
- [`initialNumToRender`](https://reactnative.dev/docs/flatlist#initialnumtorender)
- [`maxToRenderPerBatch`](https://reactnative.dev/docs/virtualizedlist#maxtorenderperbatch)
- [`recordInteraction`](https://reactnative.dev/docs/virtualizedlist#recordinteraction)
- [`setNativeProps`](https://reactnative.dev/docs/virtualizedlist#setnativeprops)
- [`updateCellsBatchingPeriod`](https://reactnative.dev/docs/virtualizedlist#updatecellsbatchingperiod)
- [`onScrollToIndexFailed`](https://reactnative.dev/docs/virtualizedlist#onscrolltoindexfailed)
- [`windowSize`](https://reactnative.dev/docs/virtualizedlist#windowsize)
We don't currently plan to implement these props.
---
### Documentation/Docs/Guides/Layout Animation (documentation/docs/guides/layout-animation.md)
---
id: layout-animation
title: LayoutAnimation
---
[`LayoutAnimation`](https://reactnative.dev/docs/layoutanimation) is a popular way how to animate views in React Native.
FlashList does support `LayoutAnimation`s but you need to call [`prepareForLayoutAnimationRender()`](/usage#prepareforlayoutanimationrender) before React Native's [`LayoutAnimation.configureNext`](https://reactnative.dev/docs/layoutanimation#configurenext). `prepareForLayoutAnimationRender` is an instance method, so you have to keep a reference to your `FlashList` instance via the [`ref`](https://reactjs.org/docs/refs-and-the-dom.html) prop:
```tsx
// This must be called before `LayoutAnimation.configureNext` in order for the animation to run properly.
listRef.current?.prepareForLayoutAnimationRender();
// After removing the item, we can start the animation.
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
```
For the animation to work properly, you additionally need to add [`keyExtractor`](/usage#keyextractor) prop to your `FlashList` component if you have not already done so.
:::note
`LayoutAnimation` is experimental on Android, so we cannot guarantee its stability when used with `FlashList`.
:::
## Example
```tsx
import React, { useRef, useState } from "react";
import { View, Text, Pressable, LayoutAnimation } from "react-native";
import { FlashList } from "@shopify/flash-list";
const List = () => {
const [data, setData] = useState([1, 2, 3, 4, 5]);
const list = useRef | null>(null);
const removeItem = (item: number) => {
setData(
data.filter((dataItem) => {
return dataItem !== item;
})
);
// This must be called before `LayoutAnimation.configureNext` in order for the animation to run properly.
list.current?.prepareForLayoutAnimationRender();
// After removing the item, we can start the animation.
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
};
const renderItem = ({ item }: { item: number }) => {
return (
{
removeItem(item);
}}
>
Cell Id: {item}
);
};
return (
{
return item.toString();
}}
renderItem={renderItem}
data={data}
/>
);
};
export default List;
```
---
### Documentation/Docs/Guides/List Profiling (documentation/docs/guides/list-profiling.md)
# List Profiling with useBenchmark
The `useBenchmark` hook provides a comprehensive way to measure and analyze the performance of your FlashList implementation. It automatically scrolls through your list while collecting performance metrics and provides actionable suggestions for optimization.
## Basic Usage
```tsx
import { useRef } from "react";
import { FlashList, FlashListRef, useBenchmark } from "@shopify/flash-list";
function MyList() {
const flashListRef = useRef>(null);
// Basic benchmark setup
useBenchmark(flashListRef, (result) => {
console.log("Benchmark complete:", result.formattedString);
});
return ;
}
```
## Manual Benchmark Control
For more control over when the benchmark runs, use the `startManually` option:
```tsx
const { startBenchmark, isBenchmarkRunning } = useBenchmark(
flashListRef,
(result) => {
if (!result.interrupted) {
Alert.alert("Benchmark Complete", result.formattedString);
}
},
{
startManually: true,
repeatCount: 3,
speedMultiplier: 1.5,
}
);
// Trigger benchmark on button press
;
```
## Configuration Options
The `useBenchmark` hook accepts an optional `BenchmarkParams` object:
| Parameter | Type | Default | Description |
| ----------------- | ------- | ------- | ----------------------------------------------------------------------- |
| `startDelayInMs` | number | 3000 | Delay before automatic benchmark start (in milliseconds) |
| `speedMultiplier` | number | 1 | Multiplier for scroll speed (higher = faster scrolling) |
| `repeatCount` | number | 1 | Number of times to repeat the benchmark |
| `startManually` | boolean | false | Prevent automatic start, use returned `startBenchmark` function instead |
## Understanding Results
The benchmark returns a `BenchmarkResult` object containing:
```typescript
interface BenchmarkResult {
js?: {
averageFPS: number;
minFPS: number;
maxFPS: number;
};
interrupted: boolean;
suggestions: string[];
formattedString?: string;
}
```
### Key Metrics
- **Average FPS**: The average JavaScript frames per second during scrolling
- **Min FPS**: The lowest FPS recorded
- **Max FPS**: The highest FPS recorded
### Interpreting Results
- **Good Performance**: Average FPS above 50
- **Acceptable Performance**: Average FPS between 35-50
- **Poor Performance**: Average FPS below 35
## Performance Suggestions
The benchmark automatically provides suggestions based on your results:
1. **Low JS FPS** (< 35 FPS): Indicates components are doing too much work. Consider:
- Optimizing render methods
- Reducing component complexity
- Implementing memoization
- Minimizing re-renders
2. **Small Dataset** (< 200 items): Testing with larger datasets provides more realistic performance metrics
## Example
```
/* Detailed source-code truncated for AI context efficiency. */
```
## Best Practices
1. **Test with Production Data**: Use realistic data sizes and complexity
2. **Run Multiple Iterations**: Use `repeatCount` for more accurate averages
3. **Test on Target Devices**: Performance varies significantly across devices
4. **Benchmark Before and After**: Compare results when making optimizations
5. **Consider User Scenarios**: Test with different scroll speeds using `speedMultiplier`
---
### Documentation/Docs/Guides/Masonry Layout (documentation/docs/guides/masonry-layout.md)
---
id: masonry
title: Masonry Layout
---
Masonry Layout allows you to create a grid of items with different heights. It is a great way to display a collection of images with different sizes.
## FlashList with masonry prop (v2)
In v2, masonry layout is enabled using the `masonry` prop on `FlashList`.
```tsx
import React from "react";
import { View, Text } from "react-native";
import { FlashList } from "@shopify/flash-list";
import { DATA } from "./data";
const MyMasonryList = () => {
return (
{item.title}}
/>
);
};
```
### With `overrideItemLayout`
When you want to customize item layout (such as setting different spans), you can use [`overrideItemLayout`](../fundamentals/usage.md#overrideitemlayout):
```tsx
import React from "react";
import { View, Text, Image } from "react-native";
import { FlashList } from "@shopify/flash-list";
interface MasonryItem {
id: string;
title: string;
height: number;
span: number; // Number of columns this item should span
imageUrl: string;
}
const MyMasonryList = () => {
return (
{
// Set the span based on the item's span property
layout.span = item.span;
// Note: In v2, size estimates are no longer needed in overrideItemLayout
// The actual height is determined by the rendered component
}}
renderItem={({ item }) => (
{item.title}
)}
keyExtractor={(item) => item.id}
/>
);
};
```
### `optimizeItemArrangement` prop
```tsx
optimizeItemArrangement?: boolean;
```
When enabled with `masonry` layout, this will try to reduce differences in column height by modifying item order. Default is `true`.
## Migration from v1
If you're migrating from v1's `MasonryFlashList`, here are the key changes:
1. **Use `FlashList` with `masonry` prop** instead of `MasonryFlashList`
2. **`overrideItemLayout` no longer needs size estimates** - only use it for setting `layout.span`
3. **`getColumnFlex` is not supported** in v2 masonry layout
4. **Item heights are determined by actual rendered component** rather than estimates
---
### Documentation/Docs/Guides/Reanimated (documentation/docs/guides/reanimated.md)
---
id: reanimated
title: React Native Reanimated
---
[React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/) is an alternative animation library to the [`LayoutAnimation`](https://reactnative.dev/docs/layoutanimation) API provided by React Native.
We support view animations and most of [layout animations](https://docs.swmansion.com/react-native-reanimated/docs/layout-animations/layout-transitions/).
## Layout Animations
For layout animations, similarly to the React Native API, you need to call [`prepareLayoutAnimationRender()`](/usage#prepareforlayoutanimationrender) before removing or inserting an element that you want to animate.
## Hooks
### Usage
You can use hooks such as [`useSharedValue`](https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue) as you would in a normal view. The difference is that since views get recycled, a value can transfer to an unrelated component. You will need to reset such values when a view is recycled - for this, you can pass a prop that uniquely identifies the cell (such as `id` of an item) and run a callback via `useEffect`. You can take inspiration from the following example:
```tsx
import React, { useEffect } from "react";
import Animated, { useSharedValue } from "react-native-reanimated";
import { FlashList } from "@shopify/flash-list";
const Item = ({ item }: { item: { id: string } }) => {
const myValue = useSharedValue(0);
useEffect(() => {
// Reset value when id changes (view was recycled for another item)
myValue.value = 0;
}, [item.id, myValue]);
return ;
};
const MyList = () => {
return ;
};
```
### Performance
If you use hooks that accept a dependencies array, make sure to leverage it and include only the minimal set of dependencies.
---
### Documentation/Docs/Guides/Section List (documentation/docs/guides/section-list.md)
---
id: section-list
title: SectionList
---
React Native has a convenience component on top of `FlatList`, called [`SectionList`](https://reactnative.dev/docs/sectionlist). This component has some additional props:
- [`sections`](https://reactnative.dev/docs/sectionlist#requiredsections)
- [`renderSectionFooter`](https://reactnative.dev/docs/sectionlist#rendersectionfooter)
- [`renderSectionHeader`](https://reactnative.dev/docs/sectionlist#rendersectionheader)
- [`SectionSeparatorComponent`](https://reactnative.dev/docs/sectionlist#sectionseparatorcomponent)
- [`stickySectionHeadersEnabled`](https://reactnative.dev/docs/sectionlist#stickysectionheadersenabled)
**`FlashList` offers none of these props but all of them are replaceable with existing props.**
The difficulty of migrating from `SectionList` to `FlashList` will depend on the data you have at hand - the data may be more suitable for `SectionList`, requiring you to massage the data, but the opposite can be true as well. In that case, using `FlashList` instead of `SectionList` might even result in less code.
Let's go through how to migrate from `SectionList` to `FlashList` in the following example - a contacts list.
This is how we could write such a list with `SectionList`:
```tsx
import React from "react";
import { SectionList, StyleSheet, Text } from "react-native";
interface Contact {
firstName: string;
lastName: string;
}
interface Section {
title: string;
data: Contact[];
}
const contacts: Section[] = [
{ title: "A", data: [{ firstName: "John", lastName: "Aaron" }] },
{
title: "D",
data: [
{ firstName: "John", lastName: "Doe" },
{ firstName: "Mary", lastName: "Dianne" },
],
},
];
const ContactsSectionList = () => {
return (
{
return {item.firstName};
}}
renderSectionHeader={({ section: { title } }) => (
{title}
)}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 32,
backgroundColor: "#fff",
},
});
```
To migrate to `FlashList`, we'd need to first convert the `contacts` variable to the following:
```tsx
const contacts: (string | Contact)[] = [
"A",
{ firstName: "John", lastName: "Aaron" },
"D",
{ firstName: "John", lastName: "Doe" },
{ firstName: "Mary", lastName: "Dianne" },
];
```
As you can see, you can add the section item right along with the data. Then in the `renderItem`, you can distinguish what to render based on the type of the item:
```tsx
const ContactsFlashList = () => {
return (
{
if (typeof item === "string") {
// Rendering header
return {item};
} else {
// Render item
return {item.firstName};
}
}}
getItemType={(item) => {
// To achieve better performance, specify the type based on the item
return typeof item === "string" ? "sectionHeader" : "row";
}}
/>
);
};
```
You can follow a similar pattern as for `renderItem` for the rest of the `SectionList`'s props.
If you want your section headers to be sticky, you will also need to compute the array for `stickyHeaderIndices`:
```tsx
const stickyHeaderIndices = contacts
.map((item, index) => {
if (typeof item === "string") {
return index;
} else {
return null;
}
})
.filter((item) => item !== null) as number[];
```
And that's it! Below you can find the whole example for `FlashList`:
```tsx
import React from "react";
import { StyleSheet, Text } from "react-native";
import { FlashList } from "@shopify/flash-list";
interface Contact {
firstName: string;
lastName: string;
}
const contacts: (string | Contact)[] = [
"A",
{ firstName: "John", lastName: "Aaron" },
"D",
{ firstName: "John", lastName: "Doe" },
{ firstName: "Mary", lastName: "Dianne" },
];
const stickyHeaderIndices = contacts
.map((item, index) => {
if (typeof item === "string") {
return index;
} else {
return null;
}
})
.filter((item) => item !== null) as number[];
const ContactsFlashList = () => {
return (
{
if (typeof item === "string") {
// Rendering header
return {item};
} else {
// Render item
return {item.firstName};
}
}}
stickyHeaderIndices={stickyHeaderIndices}
getItemType={(item) => {
// To achieve better performance, specify the type based on the item
return typeof item === "string" ? "sectionHeader" : "row";
}}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 32,
backgroundColor: "#fff",
},
});
```
---
### Documentation/Docs/Guides/Testing (documentation/docs/guides/testing.md)
---
id: testing
title: Testing with Jest
slug: /testing
---
By default `FlashList` will mount all items in the test environment. You can use the following mock to setup measurements to prevent everything from mounting. You can also create your own mock.
## Setup
Add the following line to your `jest-setup.js` file:
```js
require("@shopify/flash-list/jestSetup");
```
To be sure, check if your jest.config.js file contains:
```
...
preset: 'react-native',
setupFiles: ['./jest-setup.js'],
...
```
## Example
Here is an example of using [`@testing-library/react-native`](https://callstack.github.io/react-native-testing-library/):
```tsx
import React from "react";
import { render } from "@testing-library/react-native";
describe("MyFlashListComponent", () => {
it("renders items", () => {
const { getByText } = render();
const element = getByText("Title of one of the items");
// Do something with element ...
});
});
```
---
### Documentation/Versioned Docs/Version 1.X/Index (documentation/versioned_docs/version-1.x/index.mdx)
---
id: introduction
title: Introduction 📚
slug: /
sidebar_position: 0
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
**Fast & performant React Native list. No more blank cells.**
Swap from FlatList in seconds. Get instant performance.
## Installation
Add the package to your project via:
```bash
yarn add @shopify/flash-list
```
and run `pod install` in the `ios` directory. If you don't use React Native CLI's
[autolinking](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md)
feature, you will need to manually link the dependency - read
[here](guides/manual-linking.md) to learn more.
- ✅ This library can be used with "Expo Go" from SDK 46 upwards or with a development client
- ✅ You can use this library with [Development Builds](https://docs.expo.dev/development/introduction/). No config plugin is required.
```bash
npx expo install @shopify/flash-list expo-dev-client
```
You can then [create a new Development Build](https://docs.expo.dev/development/build/) and begin using `FlashList`.
### Usage
Read more about the usage of `FlashList` [here](fundamentals/usage.md)
### App / Playground
The [fixture](https://github.com/Shopify/flash-list/tree/main/fixture) is an example app showing how to use the library.
---
### Documentation/Versioned Docs/Version 1.X/Known Issues (documentation/versioned_docs/version-1.x/known-issues.md)
---
id: known-issues
title: Known issues
slug: /known-issues
sidebar_position: 3
---
# Known Issues
FlashList and FlatList have very different internal. While the API is almost the same, the behaviour might be different in some cases due to a bug, limitation or missing implementation. In any case, if the issue is already listed here don't create a new one.
### 1) FlashList's rendered size is not usable warning
`FlashList` uses [recyclerlistview](https://github.com/Flipkart/recyclerlistview) to leverage its recycling capability. `recyclerlistview's` default layout algorithm cannot work without a valid size. It needs to first measure itself and then decide how much to draw and reuse. So, make sure that the parent of the list mounts with a valid size (>=2px) and `FlashList` will match the size of its parent. Please note that you cannot apply style directly to `FlashList` so you may need to wrap it in a `View`.
Please note most lists do mount with deterministic sizes so make sure to check if you really need workarounds.
### 2) `onEndReached` event doesn't have `distanceFromEnd`
This value is reported as 0. We don't have plans to implement this right now. Please provide feedback if this is important to you.
### 3) `renderItem` callback doesn't have all features
As of now we only provide relevant data and index. No plans to change this.
### 4) Web support is in beta
- Layout is async so it's possible to see shift animations on list load.
- `onBlankArea` event and `useBlankAreaTracker` hook are not supported.
Exercise caution and make sure all changes are tested on web as there could be some differences when compared to Android/iOS.
### 5) `react-native-windows/macos` support
FlashList will run in JS only mode on both Windows and macOS. We don't have plans to write native code or actively test on platforms other than Android, iOS and Web.
---
### Documentation/Versioned Docs/Version 1.X/Fundamentals/Estimated Item Size (documentation/versioned_docs/version-1.x/fundamentals/estimated-item-size.md)
---
id: estimated-item-size
title: Estimated Item Size Prop
slug: /estimated-item-size
sidebar_position: 2
---
`estimatedItemSize` is a single numeric value that hints `FlashList` about the approximate size of the items before they're rendered. `FlashList` can then use this information to decide how many items it needs to draw on the screen before initial load and while scrolling. If most of the items are of **different sizes**, you can think of an average or median value and if most items are of **the same size**, just use that number. A quick look at `Element Inspector` can help you determine this. If you're confused between two values, the smaller value is a better choice.
### How to calculate
- Use the average item size from the first render - this will be a part of the warning if you don't specify `estimatedItemSize`.
- If most items are of **similar heights** or **widths** (if horizontal), just open up element inspector from `react-native's` dev menu, check the size of one of the items, and use it. Don't worry about different devices. We have enough tolerance to work around it.
- If the items **have a lot of different sizes**, choose a few that are pretty different, use element inspector, read their sizes, and calculate an average. This average will work great, don't worry about solving for different devices sizes. There's enough tolerance internally.
- If you run into a situation where more than one value seems like a good fit, pick the smaller one.
## Deep dive
`FlashList` doesn't know the size of its children before they're rendered. When the list is loading, it needs to decide how many items to render. There are only few choices here:
- **Render a fixed number of items:** A default number of items to render on load may not be right for all devices and it should ideally depend on screen size. Drawing smaller number of items will show up blank space on load and drawing too many will increase load time.
- **Use a default value for `estimatedItemSize`:** If we use a default size for items on launch, we still run into problems. Let's say we use `50px` as the default, without you being aware of this, and you're rendering a list with large items like a news feed where items are complex. Let's say the actual rendered size is around `500px`. Based on this assumption, we will draw `20` items on load if the screen size is `1000px` while we should only draw 2. This result is not optimal, and you may not even realize how much faster loads can be.
- **Take `estimatedItemSize` from developers:** Instead of trying to hide away this requirement, we're letting you make the decision. We have decided to use a size estimate and not an initial count to render because size is a more stable value across device sizes.
We currently keep `estimatedItemSize` optional but **strongly** recommend you to set it. If you don't, you will get a warning with suggestion to set it to a value equal to the average item size from the first render.
### Impact on scroll
During very quick scrolls, if offsets are changing very quickly, `FlashList` might run into a situation where it needs to prepare more than one item. This is just another version of the same problem. If your estimates are too big compared to actual sizes, `FlashList` might think that a small number of items is enough to fill the screen and you might see blanks. _This is the primary reason we suggest using a smaller value if you're confused between the two of them._ Drawing a few more items is better than showing blanks. With `FlashList`, we don't expect blanks unless components are very slow or `estimatedItemSize` is too big compared to actual sizes.
### Impact of number of items drawn on responsiveness
Having a few items on the screen is great for performance and responsiveness. Small render tree is much faster to update. Let's say there's a checkbox within your list items and you store their selected state in a store. You'd want this checkbox to be extremely responsive while changing states and having a large render tree will prevent that from happening. Many of you might have seen this problem in `FlatList`.
`FlatList` has a default [`windowSize`](https://reactnative.dev/docs/optimizing-flatlist-configuration#windowsize) of [`21`](https://reactnative.dev/docs/optimizing-flatlist-configuration#windowsize) which means that, on a `1000px` tall device, it will draw about `10,000px` at the bottom and at the top of currently visible window. `FlashList` in comparison will only draw `250px` extra on the top and bottom irrespective of the screen size. You will be amazed with how responsive things become when we have a very small number of items and that's why we care so much about it.
### Impact of having wrong `estimatedItemSize`
- Please note `FlashList` will not overlap or show gaps between items due to incorrect values provided here.
- **If the estimate is much higher than actual values**, you may see few items load on screen and then immediately more will show up. During fast scroll you may see some blank area. It's not because things have become slow. The list just doesn't know that it has not drawn enough. Once the list knows the actual sizes of items it won't rely on estimates and that's why on scrolling up you may not see the same problem.
- **If the estimate is much smaller than actual values**, not much will change visibly but you will be drawing more than necessary. If your components are complex, load times might increase.
### Future Revisions
We're looking at ways to remove this requirement by leveraging `Fabric`. In the short term, we plan to compute this average after the initial load to prevent issues during quick scrolls. The value provided will be also more relevant for load time optimization.
---
### Documentation/Versioned Docs/Version 1.X/Fundamentals/Metrics (documentation/versioned_docs/version-1.x/fundamentals/metrics.md)
---
id: metrics
title: Metrics
sidebar_position: 1
---
FlashList enables you to track metrics in production that can give you more insight into how your app is doing in terms of performance. We recommend that you track the following in production:
### Visible blank area
FlashList comes with a hook that can track cumulative and maximum blank space that the user experienced while scrolling the list. The cost of tracking this metric is minimal and you can implement it in the following way:
```tsx
const MyComponent = () => {
// `any` is the type of data. You can mention the type of data that you're using with your FlashList implementation.
const ref = useRef>(null);
// The tracking will happen for the entire lifecycle of the list and the result object will always have the latest values.
// You can make a call when to ingest this data. We recommend that you ingest when the list unmounts.
const [blankAreaTrackerResult, onBlankArea] = useBlankAreaTracker(ref);
useEffect(() => {
return () => {
// When component is being cleaned up, you can ingest the result into your analytics system.
// blankAreaTrackerResult has two fields - `cumulativeBlankArea` and `maxBlankArea`. `cumulativeBlankArea` is the total blank area that the user has seen while scrolling the list.
// maxBlankArea is the maximum blank area that the user has seen while scrolling the list.
ingestData(blankAreaTrackerResult);
};
}, []);
// pass the listener returned by the hook to FlashList
return ;
};
```
You can rest assured when you see close to zero blank space in production. If you're not happy with the numbers, please refer to our [performance troubleshooting guide](performance-troubleshooting.md) which can help you optimize your list's performance.
### Load time
FlashList has a built in `onLoad` event that you can use to track the time taken to load the list. This tracks elapsed time from the point the list was created to the time when it's children are visible to the user.
```tsx
const MyComponent = () => {
const onLoadListener = useCallback(({ elapsedTimeInMs } ) => {
ingestData("Sample List load time", elapsedTimeInMs);
}, []);
return ;
```
### Sampling
Please note that you can always sample data collected by your implementation. It's possible to get an accurate picture of how your app is performing by collecting data from a subset of users. This is important incase you want to limit how much data you collect.
---
### Documentation/Versioned Docs/Version 1.X/Fundamentals/Performance Troubleshooting (documentation/versioned_docs/version-1.x/fundamentals/performance-troubleshooting.md)
---
id: performance-troubleshooting
title: Performance troubleshooting
sidebar_position: 1
---
## Profiling
:::warning
Before assessing your list's performance, make sure you are in release mode. On Android, you can disable JS dev mode inside the developer menu, whereas you need to run the release configuration on iOS.
FlashList can appear to be slower than FlatList in dev mode. The primary reason is a much smaller and fixed [window size](https://reactnative.dev/docs/virtualizedlist#windowsize) equivalent. Click [here](https://reactnative.dev/docs/performance#running-in-development-mode-devtrue) to know more about why you shouldn't profile with dev mode on.
:::
The following metrics can be used for profiling the list's overall performance:
- **blank area - a size of a visible blank area on scroll.** Use the built-in [`onBlankArea`](usage.md#onblankarea) event to get it reported. You can also use this event for tracking this metric in production. Alternatively, you can use [react-native-performance-lists-profiler](https://shopify.github.io/react-native-performance/docs/guides/react-native-performance-lists-profiler/) package which also comes with a Flipper plugin.
- **TTI - time-to-interactive of the list.** Comes along with blank area as part of [react-native-performance-lists-profiler](https://shopify.github.io/react-native-performance/docs/guides/react-native-performance-lists-profiler/) package. This is a great option for local profiling - however, we do not recommend using it currently in production.
- **FPS - frames per second.** For both native and JS FPS, you can either use the built-in performance monitor or we recommend [this](https://github.com/bamlab/react-native-performance) opensource plugin. Using native profilers in Xcode and Android Studio is a yet-another option but they track only the native FPS.
## How to improve performance
If the numbers indicate that the performance is not good enough, you should act - **continue [here](performant-components.md) to learn more about how to optimize your list.**
---
### Documentation/Versioned Docs/Version 1.X/Fundamentals/Performant Components (documentation/versioned_docs/version-1.x/fundamentals/performant-components.md)
---
id: performant-components
title: Writing performant components
---
While `FlashList` does its best to achieve high performance, it will still perform poorly if your item components are slow to render. In this post, let's dive deeper into how you can remedy this.
## 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. When optimizing your item component, try to ensure as few things as possible have to be re-rendered and recomputed when recycling.
## Optimizations
There's lots of optimizations that are applicable for _any_ React Native component and which might help render times of your item components as well. Usage of `useCallback`, `useMemo`, and `useRef` is advised - but don't use these blindly, always [measure the performance](performance-troubleshooting.md) before and after making your changes.
:::note
Always profile performance in the release mode. `FlashList`'s performance between JS dev and release mode differs greatly.
:::
### `estimatedItemSize`
Ensure [`estimatedItemSize`](usage.md#estimateditemsize) is as close as possible to the real average value - see [here](estimated-item-size.md#how-to-calculate) how to properly calculate the value for this prop.
### Remove `key` prop
:::warning
Using `key` prop inside your item and item's nested components will highly degrade performance.
:::
Make sure your **item components and their nested components don't have a `key` prop**. Using this prop will lead to `FlashList` not being able to recycle views, losing all the benefits of using it over `FlatList`.
For example, if we had a following item component:
```tsx
const MyNestedComponent = ({ item }) => {
return I am nested!;
};
const MyItem = ({ item }) => {
return (
{item.title}
);
};
```
Then the `key` prop should be removed from both `MyItem` and `MyNestedComponent`:
```tsx
const MyNestedComponent = ({ item }) => {
return I am nested!;
};
const MyItem = ({ item }) => {
return (
{item.title}
);
};
```
There might be cases where React forces you to use `key` prop, such as when using `map`. In such cirumstances, ensure that the `key` is not tied to the `item` prop in any way, so the keys don't change when recycling.
Let's imagine we want to display names of users:
```tsx
const MyItem = ({ item }: { item: any }) => {
return (
<>
{item.users.map((user: any) => {
{user.name};
})}
>
);
};
```
If we wrote our item component like this, the `Text` component would need to be re-created. Instead, we can do the following:
```tsx
const MyItem = ({ item }) => {
return (
<>
{item.users.map((user, index) => {
/* eslint-disable-next-line react/no-array-index-key */
{user.name};
})}
>
);
};
```
Although using index as a `key` in `map` is not recommended by React, in this case since the data is derived from the list's data, the items will update correctly.
### Difficult calculations
If you do any calculations that might take a lot of resources, consider memoizing it, making it faster, or removing it altogether. The render method of items should be as efficient as possible:
### `getItemType`
**If you have different types of cell components and these are vastly different, consider leveraging the [`getItemType` prop](usage.md#getitemtype)**. For example, if we were building a messages list, we could write it like this:
```tsx
// A message can be either a text or an image
enum MessageType {
Text,
Image,
}
interface TextMessage {
text: string;
type: MessageType.Text;
}
interface ImageMessage {
image: ImageSourcePropType;
type: MessageType.Image;
}
type Message = ImageMessage | TextMessage;
const MessageItem = ({ item }: { item: Message }) => {
switch (item.type) {
case MessageType.Text:
return {item.text};
case MessageType.Image:
return ;
}
};
// Rendering the actual messages list
const MessageList = () => {
return ;
};
```
However, this implementation has one performance drawback. When the list recycles items and the `MessageType` changes from `Text` to `Image` or vice versa, React won't be able to optimize the re-render since the whole render tree of the item component changes. We can fix this by changing the `MessageList` to this:
```tsx
const MessageList = () => {
return (
{
return item.type;
}}
/>
);
};
```
`FlashList` will now use separate recycling pools based on `item.type`. That means we will never recycle items of different types, making the re-render faster.
### Leaf components
Let's consider the following example:
```tsx
const MyHeavyComponent = () => {
return ...;
};
const MyItem = ({ item }) => {
return (
<>
{item.title}
>
);
};
```
Since `MyHeavyComponent` does not directly depend on the `item` prop, `memo` can be used to skip re-rending `MyHeavyComponent` when the item is recycled and thus re-rendered:
```tsx
const MyHeavyComponent = () => {
return ...;
};
const MemoizedMyHeavyComponent = memo(MyHeavyComponent);
const MyItem = ({ item }: { item: any }) => {
return (
<>
{item.title}
>
);
};
```
---
### Documentation/Versioned Docs/Version 1.X/Fundamentals/Recycling (documentation/versioned_docs/version-1.x/fundamentals/recycling.md)
---
id: recycling
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:
```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);
}
return (
setLiked(true)}>
{liked}
);
};
```
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.
---
### Documentation/Versioned Docs/Version 1.X/Fundamentals/Usage (documentation/versioned_docs/version-1.x/fundamentals/usage.md)
---
id: usage
title: Usage
slug: /usage
sidebar_position: 0
---
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:
```tsx
import React from "react";
import { View, Text, StatusBar } from "react-native";
import { FlashList } from "@shopify/flash-list";
const DATA = [
{
title: "First Item",
},
{
title: "Second Item",
},
];
const MyList = () => {
return (
{item.title}}
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`](#renderitem) hierarchy for explicit `key` prop definitions and remove them. If you're doing a `.map()` use indices as keys.
3. Check your [`renderItem`](#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`](#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.
:::note `estimatedItemSize`
[`estimatedItemSize`](#estimateditemsize) is necessary to achieve optimal performance.
:::
Most of the props from `FlatList` are available in `FlashList`, too. This documentation includes both `FlatList` and additional `FlashList` props and should be used as a primary reference. But you can also read more about the props available in both `FlatList` and `FlashList` [here](https://reactnative.dev/docs/flatlist).
# Props
`FlashList` also has a couple of unique props. You already know about `estimatedItemSize` but there are more props available for minor adjustments.
### **`renderItem`**
:::note
Required
:::
```tsx
renderItem: ({ item, index, target, extraData }) => void;
```
Takes an item from `data` and renders it into the list. Typical usage:
```tsx
renderItem = ({item}) => (
{item.title}
);
...
```
Provides additional metadata like `index`
- `item` (`Object`): The item from `data` being rendered.
- `index` (`number`): The index corresponding to this item in the `data` array.
- `target` (`string`) FlashList may render your items for multiple reasons.
- `Cell` - This is for your list item.
- `Measurement` - Might be invoked for size measurement and won't be visible. You can ignore this in analytics.
- `StickyHeader` - This is for your sticky header. Use this to change your item's appearance while it's being used as a sticky header.
- `extraData` (`Object`) - This is the same `extraData` prop that was passed to `FlashList`.
### **`data`**
:::note
Required
:::
For simplicity, data is a plain array of items of a given type.
```tsx
data: ItemT[];
```
### **`estimatedItemSize`**
```ts
estimatedItemSize?: number;
```
`estimatedItemSize` is a single numeric value that hints `FlashList` about the approximate size of the items before they're rendered. `FlashList` can then use this information to decide how many items it needs to draw on the screen before initial load and while scrolling. If most of the items are of **different sizes**, you can think of an average or median value and if most items are of **the same size**, just use that number. A quick look at `Element Inspector` can help you determine this. If you're confused between two values, the smaller value is a better choice. If you don't specify this prop, you will get a warning with a value you can use. We recommend not ignoring that warning and defining `estimatedItemSize` before the list gets to your users.
---
### `CellRendererComponent`
Each cell is rendered using this element. Can be a React Component Class, or a render function. The root component should always be a `CellContainer` which is also the default component used. Ensure that the original `props` are passed to the returned `CellContainer`. The `props` contain the following properties:
- `onLayout`: Method for updating data about the real `CellContainer` layout
- `index`: Index of the cell in the list, you can use this to query data if needed
- `style`: Style of `CellContainer`, including:
- `flexDirection`: Depends on whether your list is horizontal or vertical
- `position`: Value of this will be `absolute` as that's how `FlashList` positions elements
- `left`: Determines position of the element on x axis
- `top`: Determines position of the element on y axis
- `width`: Determines width of the element (present when list is vertical)
- `height`: Determines height of the element (present when list is horizontal)
When using with `react-native-reanimated`, you can wrap `CellContainer` in `Animated.createAnimatedComponent` (this is similar to using `Animated.View`):
:::note
Changing layout of the cell can conflict with the native layout operations. You may need to set `disableAutoLayout` to `true` to prevent this.
:::
```ts
const AnimatedCellContainer = Animated.createAnimatedComponent(CellContainer);
return (
{
return (
);
}}
/>
);
```
```ts
CellRendererComponent?: React.ComponentType | undefined;
```
### `ItemSeparatorComponent`
Rendered in between each item, but not at the top or bottom. By default, `leadingItem` and `trailingItem` (if available) props are provided.
```tsx
ItemSeparatorComponent?: React.ComponentType;
```
### `ListEmptyComponent`
Rendered when the list is empty. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``).
```tsx
ListEmptyComponent?: React.ComponentType | React.ReactElement>;
```
### `ListFooterComponent`
Rendered at the bottom of all the items. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``).
```tsx
ListFooterComponent?: React.ComponentType | React.ReactElement>;
```
### `ListFooterComponentStyle`
Styling for internal View for `ListFooterComponent`.
```tsx
ListFooterComponentStyle?: StyleProp;
```
### `ListHeaderComponent`
Rendered at the top of all the items. Can be a React Component (e.g. `SomeComponent`), or a React element (e.g. ``).
```tsx
ListHeaderComponent?: React.ComponentType | React.ReactElement>;
```
### `ListHeaderComponentStyle`
Styling for internal View for `ListHeaderComponent`.
```tsx
ListHeaderComponentStyle?: StyleProp;
```
### `contentContainerStyle`
```tsx
contentContainerStyle?: ContentStyle;
export type ContentStyle = Pick<
ViewStyle,
| "backgroundColor"
| "paddingTop"
| "paddingLeft"
| "paddingRight"
| "paddingBottom"
| "padding"
| "paddingVertical"
| "paddingHorizontal"
>;
```
You can use `contentContainerStyle` to apply padding that will be applied to the whole content itself. For example, you can apply this padding, so that all of your items have leading and trailing space.
### `disableAutoLayout`
```tsx
disableAutoLayout?: boolean;
```
FlashList applies some fixes to layouts of its children which can conflict with custom `CellRendererComponent` implementations. You can disable this behavior by setting this to `true`.
:::note
Recommendation: Set this to `true` while you apply special behavior to the `CellRendererComponent`. Once done set this to `false` again.
:::
### `disableHorizontalListHeightMeasurement`
```tsx
disableHorizontalListHeightMeasurement?: boolean;
```
FlashList attempts to measure size of horizontal lists by drawing an extra list item in advance. This can sometimes cause issues when used with `initialScrollIndex` in lists with very little content. You might see some amount of over scroll. When set to true the list's rendered size needs to be deterministic (i.e., height and width greater than 0) as FlashList will skip rendering the extra item for measurement. Default value is `false`.
### `drawDistance`
```tsx
drawDistance?: number;
```
Draw distance for advanced rendering (in `dp`/`px`).
### `estimatedFirstItemOffset`
```tsx
estimatedFirstItemOffset?: number;
```
`estimatedFirstItemOffset` specifies how far the first item is drawn from start of the list window or offset of the first item of the list (not the header). This prop is necessary if you're using [initialScrollIndex](#initialscrollindex) prop. Before the initial draw, the list does not know the size of a header or any special margin/padding that might have been applied using header styles etc. If this isn't provided initialScrollIndex might not scroll to the provided index.
### `estimatedListSize`
```tsx
estimatedListSize?: { height: number; width: number }
```
Estimated visible height and width of the list. It is not the scroll content size. Defining this prop will enable the list to be rendered immediately. Without it, the list first needs to measure its size, leading to a small delay during the first render.
### `extraData`
A marker property for telling the list to re-render (since it implements `PureComponent`). If any of your `renderItem`, Header, Footer, etc. functions depend on anything outside of the `data` prop, stick it here and treat it immutably.
```tsx
extraData?: any;
```
### `horizontal`
If `true`, renders items next to each other horizontally instead of stacked vertically. Default is `false`.
```tsx
horizontal?: boolean;
```
### `initialScrollIndex`
Instead of starting at the top with the first item, start at `initialScrollIndex`.
```tsx
initialScrollIndex?: number;
```
### `inverted`
Reverses the direction of scroll. Uses scale transforms of `-1`.
```tsx
inverted?: boolean;
```
### `keyExtractor`
```tsx
keyExtractor?: (item: object, index: number) => string;
```
Used to extract a unique key for a given item at the specified index. Key is used for optimizing performance. Defining `keyExtractor` is also necessary when doing [layout animations](../guides/layout-animation.md) to uniquely identify animated components.
### `numColumns`
Multiple columns can only be rendered with `horizontal={false}` and will zig-zag like a `flexWrap` layout. Items should all be the same height - masonry layouts are not supported.
`numColumns?: number;`
### `onBlankArea`
```tsx
onBlankArea?: (blankAreaEvent: {
offsetStart: number;
offsetEnd: number;
blankArea: number;
}) => void;
```
`FlashList` computes blank space that is visible to the user during scrolling or the initial loading of the list.
Values reported:
- `offsetStart`: visible blank space on top of the screen (while going up). If value is greater than 0, it's visible to user.
- `offsetEnd`: visible blank space at the end of the screen (while going down). If value is greater than 0, it's visible to user.
- `blankArea`: maximum of `offsetStart` and `offsetEnd`. You might see negative values indicating that items are rendered outside the list's visible area.
:::warning
This callback will be triggered even if the blanks are excepted - for example, when the list does not have enough items to fill the screen.
:::
:::note
This event isn't synced with `onScroll` event from the JS layer but works with native methods `onDraw` (Android) and `layoutSubviews` (iOS).
:::
### `onEndReached`
```tsx
onEndReached?: () => void;
```
Called once when the scroll position gets within `onEndReachedThreshold` of the rendered content.
### `onEndReachedThreshold`
```tsx
onEndReachedThreshold?: number;
```
How far from the end (in units of visible length of the list) the bottom edge of the list must be from the end of the content to trigger the `onEndReached` callback. Thus a value of 0.5 will trigger `onEndReached` when the end of the content is within half the visible length of the list.
### `onLoad`
```tsx
onLoad: (info: { elapsedTimeInMs: number }) => void;
```
This event is raised once the list has drawn items on the screen. It also reports elapsedTimeInMs which is the time it took to draw the items. This is required because FlashList doesn't render items in the first cycle. Items are drawn after it measures itself at the end of first render. If you're using ListEmptyComponent, this event is raised as soon as ListEmptyComponent is rendered.
### `onViewableItemsChanged`
```tsx
interface ViewToken {
index: number;
isViewable: boolean;
item: string;
key: string;
timestamp: number;
}
onViewableItemsChanged?: ((info: {
viewableItems: ViewToken[];
changed: ViewToken[];
}) => void) | null | undefined
```
Called when the viewability of rows changes, as defined by the `viewabilityConfig` prop. Array of `changed` includes `ViewToken`s that both visible and non-visible items. You can use the `isViewable` flag to filter the items.
:::note
If you are tracking the time a view becomes (non-)visible, use the `timestamp` property. We make no guarantees that in the future viewability callbacks will be invoked as soon as they happen - for example, they might be deferred until JS thread is less busy.
:::
### `onRefresh`
```tsx
onRefresh?: () => void;
```
If provided, a standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the `refreshing` prop correctly.
### `getItemType`
```tsx
getItemType?: (
item: T,
index: number,
extraData?: any
) => string | number | undefined;
```
Allows developers to specify item types. This will improve recycling if you have different types of items in the list. Right type will be used for the right item.Default type is 0. If you don't want to change for an indexes just return undefined. You can see example of how to use this prop [here](performant-components.md#getitemtype).
:::warning Performance
This method is called very frequently. Keep it fast.
:::
### `overrideItemLayout`
```tsx
overrideItemLayout?: (
layout: { span?: number; size?: number },
item: T,
index: number,
maxColumns: number,
extraData?: any
) => void;
```
This method can be used to provide explicit size estimates or change column span of an item.
Providing specific estimates is a good idea when you can calculate sizes reliably. FlashList will prefer this value over `estimatedItemSize` for that specific item.
Precise estimates will also improve precision of `scrollToIndex` method and `initialScrollIndex` prop. If you have a `separator` below your items, you can include its size in the estimate.
Changing item span is useful when you have grid layouts (numColumns > 1) and you want few items to be bigger than the rest.
Modify the given layout. Do not return any value from the method. FlashList will fallback to default values if this is ignored.
:::warning Performance
This method is called very frequently. Keep it fast.
:::
### `overrideProps`
```tsx
overrideProps?: object;
```
We do not recommend using this prop for anything else than debugging. Internal props of the list will be overriden with the provided values.
### `progressViewOffset`
```tsx
progressViewOffset?: number;
```
Set this when offset is needed for the loading indicator to show correctly.
### `refreshControl`
```tsx
refreshControl?: React.ReactElement>;
```
A custom refresh control element. When set, it overrides the default `` component built internally. The onRefresh and refreshing props are also ignored. Only works for vertical VirtualizedList.
### `refreshing`
```tsx
refreshing?: boolean;
```
Set this true while waiting for new data from a refresh.
### `renderScrollComponent`
```tsx
import type { ScrollViewProps } from "react-native";
renderScrollComponent?:
| React.ComponentType
| React.FC;
```
Rendered as the main scrollview.
### `viewabilityConfig`
```tsx
interface ViewabilityConfig: {
minimumViewTime: number;
viewAreaCoveragePercentThreshold: number;
itemVisiblePercentThreshold: number;
waitForInteraction: boolean;
}
viewabilityConfig?: ViewabilityConfig;
```
`viewabilityConfig` is a default configuration for determining whether items are viewable.
:::warning
Changing viewabilityConfig on the fly is not supported
:::
Example:
```tsx
void)
| null;
}
viewabilityConfigCallbackPairs: ViewabilityConfigCallbackPairs | undefined;
```
List of `ViewabilityConfig`/`onViewableItemsChanged` pairs. A specific `onViewableItemsChanged` will be called when its corresponding `ViewabilityConfig`'s conditions are met.
# FlashList methods
### `prepareForLayoutAnimationRender()`
```tsx
prepareForLayoutAnimationRender(): void;
```
Run this method before running layout animations, such as when animating an element when deleting it. This method disables recycling for the next frame so that layout animations run well.
:::warning
Avoid using this when making large changes to the data as the list might draw too much to run animations since the method disables recycling temporarily. Single item insertions or deletions should animate smoothly. The render after animation will enable recycling again and you can stop avoiding making large data changes.
:::
### `recordInteraction()`
```tsx
recordInteraction();
```
Tells the list an interaction has occurred, which should trigger viewability calculations, e.g. if `waitForInteractions` is true and the user has not scrolled. You should typically call `recordInteraction()` when user for example taps on an item or invokes a navigation action.
### `recomputeViewableItems()`
```tsx
recomputeViewableItems();
```
Retriggers viewability calculations. Useful to imperatively trigger viewability calculations.
### `scrollToEnd()`
```tsx
scrollToEnd?: (params?: { animated?: boolean | null | undefined });
```
Scrolls to the end of the content.
### `scrollToIndex()`
```tsx
scrollToIndex(params: {
animated?: boolean | null | undefined;
index: number;
viewOffset?: number | undefined;
viewPosition?: number | undefined;
});
```
Scroll to a given index.
### `scrollToItem()`
```tsx
scrollToItem(params: {
animated?: boolean | null | undefined;
item: any;
viewPosition?: number | undefined;
});
```
Scroll to a given item.
### `scrollToOffset()`
```tsx
scrollToOffset(params: {
animated?: boolean | null | undefined;
offset: number;
});
```
Scroll to a specific content pixel offset in the list.
Param `offset` expects the offset to scroll to. In case of `horizontal` is true, the offset is the x-value, in any other case the offset is the y-value.
Param `animated` (`false` by default) defines whether the list should do an animation while scrolling.
# ScrollView props
`FlashList`, as `FlatList`, uses `ScrollView` under the hood. You can take a look into the React Native documentation for [`ScrollView`](https://reactnative.dev/docs/scrollview) to see the exhaustive list of props.
# Unsupported `FlatList` props
The following props from `FlatList` are currently not implemented:
- [`columnWrapperStyle`](https://reactnative.dev/docs/flatlist#columnwrapperstyle)
- [`debug`](https://reactnative.dev/docs/virtualizedlist#debug)
- [`listKey`](https://reactnative.dev/docs/virtualizedlist#listkey)
- [`onScrollToIndexFailed`](https://reactnative.dev/docs/virtualizedlist#onscrolltoindexfailed)
- [`windowSize`](https://reactnative.dev/docs/virtualizedlist#windowsize)
Unsupported methods:
- [`flashScrollIndicators()`](https://reactnative.dev/docs/flatlist#flashscrollindicators)
- [`hasMore`](https://reactnative.dev/docs/virtualizedlist#hasmore)
- [`getChildContext`](https://reactnative.dev/docs/virtualizedlist#getchildcontext)
- [`getNativeScrollRef()`](https://reactnative.dev/docs/flatlist#getnativescrollref)
- [`getScrollRef`](https://reactnative.dev/docs/virtualizedlist#getscrollref)
- [`getScrollResponder()`](https://reactnative.dev/docs/flatlist#getscrollresponder)
There are also `FlatList` props that would bring no value if ported to `FlashList` due to the differences in their underlying implementation:
- [`disableVirtualization`](https://reactnative.dev/docs/virtualizedlist#disablevirtualization)
- [`getItemLayout`](https://reactnative.dev/docs/flatlist#getItemLayout)
- [`initialNumToRender`](https://reactnative.dev/docs/flatlist#initialnumtorender)
- [`maxToRenderPerBatch`](https://reactnative.dev/docs/virtualizedlist#maxtorenderperbatch)
- [`recordInteraction`](https://reactnative.dev/docs/virtualizedlist#recordinteraction)
- [`setNativeProps`](https://reactnative.dev/docs/virtualizedlist#setnativeprops)
- [`updateCellsBatchingPeriod`](https://reactnative.dev/docs/virtualizedlist#updatecellsbatchingperiod)
We don't currently plan to implement these props.
---
### Documentation/Versioned Docs/Version 1.X/Guides/Layout Animation (documentation/versioned_docs/version-1.x/guides/layout-animation.md)
---
id: layout-animation
title: LayoutAnimation
---
[`LayoutAnimation`](https://reactnative.dev/docs/layoutanimation) is a popular way how to animate views in React Native.
FlashList does support `LayoutAnimation`s but you need to call [`prepareForLayoutAnimationRender()`](../fundamentals/usage.md#prepareforlayoutanimationrender) before React Native's [`LayoutAnimation.configureNext`](https://reactnative.dev/docs/layoutanimation#configurenext). `prepareForLayoutAnimationRender` is an instance method, so you have to keep a reference to your `FlashList` instance via the [`ref`](https://reactjs.org/docs/refs-and-the-dom.html) prop:
```tsx
// This must be called before `LayoutAnimation.configureNext` in order for the animation to run properly.
listRef.current?.prepareForLayoutAnimationRender();
// After removing the item, we can start the animation.
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
```
For the animation to work properly, you additionally need to add [`keyExtractor`](../fundamentals/usage.md#keyextractor) prop to your `FlashList` component if you have not already done so.
:::note
`LayoutAnimation` is experimental on Android, so we cannot guarantee its stability when used with `FlashList`.
:::
## Example
```tsx
import React, { useRef, useState } from "react";
import { View, Text, Pressable, LayoutAnimation } from "react-native";
import { FlashList } from "@shopify/flash-list";
const List = () => {
const [data, setData] = useState([1, 2, 3, 4, 5]);
const list = useRef | null>(null);
const removeItem = (item: number) => {
setData(
data.filter((dataItem) => {
return dataItem !== item;
})
);
// This must be called before `LayoutAnimation.configureNext` in order for the animation to run properly.
list.current?.prepareForLayoutAnimationRender();
// After removing the item, we can start the animation.
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
};
const renderItem = ({ item }: { item: number }) => {
return (
{
removeItem(item);
}}
>
Cell Id: {item}
);
};
return (
{
return item.toString();
}}
renderItem={renderItem}
estimatedItemSize={100}
data={data}
/>
);
};
export default List;
```
---
### Documentation/Versioned Docs/Version 1.X/Guides/Manual Linking (documentation/versioned_docs/version-1.x/guides/manual-linking.md)
---
id: manual-linking
title: Manual linking
---
## iOS
### Using 'Pods'
- Navigate to your iOS folder `cd ios`
- Add this line to your `Podfile` just below the last pod:
```ruby
pod 'RNFlashList', :path => '../node_modules/@shopify/flash-list'
```
- Run `pod install`
## Android
- Add project to `android/settings.gradle`:
```
include ':@shopify-flash-list'
project(':@shopify-flash-list').projectDir = new File(rootProject.projectDir, '../node_modules/@shopify/flash-list/android')
```
- In `android/app/build.gradle`, add to dependencies:
```diff
...
dependencies {
...
+ implementation project(':@shopify-flash-list')
}
```
- Finally, in `android/app/src/main/java/your/package/MainApplication.kt`:
```diff
package com.myapp;
+ import com.shopify.reactnative.flash_list.ReactNativeFlashListPackage
...
override fun getPackages(): List {
val packages = PackageList(this).packages
packages.add(ReactNativeFlashListPackage());
return packages
}
```
---
### Documentation/Versioned Docs/Version 1.X/Guides/Masonry Layout (documentation/versioned_docs/version-1.x/guides/masonry-layout.md)
---
id: masonry
title: Masonry Layout
---
Masonry Layout allows you to create a grid of items with different heights. It is a great way to display a collection of images with different sizes.
To get started, import `MasonryFlashList` from `@shopify/flash-list` and use it just like you would use `FlashList`:
```tsx
import React from "react";
import { View, Text, StatusBar } from "react-native";
import { MasonryFlashList } from "@shopify/flash-list";
import { DATA } from "./data";
const MyMasonryList = () => {
return (
{item.title}}
estimatedItemSize={200}
/>
);
};
```
**Note:** If you want `MasonryFlashList` to optimize item arrangement, enable `optimizeItemArrangement` and pass a valid [`overrideItemLayout`](../fundamentals/usage.md#overrideitemlayout) function.
## Unsupported Props
There are some props that `MasonryFlashList` does not support when compared to `FlashList`:
- [`horizontal`](../fundamentals/usage.md#horizontal)
- [`inverted`](../fundamentals/usage.md#inverted)
- [`initialScrollIndex`](../fundamentals/usage.md#initialscrollindex)
- [`viewabilityConfigCallbackPairs`](../fundamentals/usage.md#viewabilityconfigcallbackpairs)
- [`onBlankArea`](../fundamentals/usage.md#onblankarea)
## Additional Props
`MasonryFlashList` supports these additional props on top of `FlashList`:
### `optimizeItemArrangement`
```tsx
optimizeItemArrangement?: boolean;
```
If enabled, MasonryFlashList will try to reduce difference in column height by modifying item order. If `true`, specifying [`overrideItemLayout`](../fundamentals/usage.md#overrideitemlayout) is required. Default value is `false`.
### `getColumnFlex`
```tsx
getColumnFlex?: (
items: T[],
columnIndex: number,
maxColumns: number,
extraData?: any
) => number;
```
`getColumnFlex` allows you to change the column widths of the list. This is helpful if you want some columns to be wider than the others.
Example:
```tsx
// if `numColumns` is `3`, you can return `2` for `index 1` and `1` for the rest to achieve a `1:2:1` split by width.
getColumnFlex={(items, index, maxColumns, extraData) => {
return index === 1 ? 2 : 1;
}}
```
## Additional information in `renderItem` arguments
```tsx
export interface MasonryListRenderItemInfo
extends ListRenderItemInfo {
columnSpan: number;
columnIndex: number;
}
```
When using `MasonryFlashList` the `renderItem` prop callback will be called with two additional properties on the `info` object.
`columnIndex`: A number representing the index of the column in which the item is rendered. When using `optimizeItemArrangement` this becomes more important as the items are no longer spread linearly across the columns.
`columnSpan`: A number representing how many columns a given item may span, for now this will always return `1`.
## Methods
`MasonryFlashList` exposes the some methods that `FlashList` does. These are:
### `scrollToEnd()`
```tsx
scrollToEnd?: (params?: { animated?: boolean | null | undefined });
```
Scrolls to the end of the content.
### `scrollToOffset()`
```tsx
scrollToOffset(params: {
animated?: boolean | null | undefined;
offset: number;
});
```
Scroll to a specific content pixel offset in the list.
Parameter `offset` expects the offset to scroll to.
Parameter `animated` (`false` by default) defines whether the list should animate while scrolling.
---
### Documentation/Versioned Docs/Version 1.X/Guides/Reanimated (documentation/versioned_docs/version-1.x/guides/reanimated.md)
---
id: reanimated
title: React Native Reanimated
---
[React Native Reanimated](https://www.reanimated2.com/) is an alternative animation library to the [`LayoutAnimation`](https://reactnative.dev/docs/layoutanimation) API provided by React Native.
We support view animations and most of [layout animations](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/layout_animations/).
## Layout Animations
For layout animations, similarly to the React Native API, you need to call [`prepareLayoutAnimationRender()`](../fundamentals/usage.md#prepareforlayoutanimationrender) before removing or inserting an element that you want to animate. Note that we currently support only [entering](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/entryAnimations) and [exiting](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/exitAnimations) animations. **[Layout transitions](https://docs.swmansion.com/react-native-reanimated/docs/api/LayoutAnimations/layoutTransitions) are not supported** as of now.
## Hooks
### Usage
You can use hooks such as [`useSharedValue`](https://docs.swmansion.com/react-native-reanimated/docs/api/hooks/useSharedValue) as you would in a normal view. The difference is that since views get recycled, a value can transfer to an unrelated component. You will need to reset such values when a view is recycled - for this, you can pass a prop that uniquely identifies the cell (such as `id` of an item) and run a callback via `useEffect`. You can take inspiration from the following example:
```tsx
import React, { useEffect } from "react";
import Animated, { useSharedValue } from "react-native-reanimated";
import { FlashList } from "@shopify/flash-list";
const Item = ({ item }: { item: { id: string } }) => {
const myValue = useSharedValue(0);
useEffect(() => {
// Reset value when id changes (view was recycled for another item)
myValue.value = 0;
}, [item.id, myValue]);
return ;
};
const MyList = () => {
return ;
};
```
### Performance
If you use hooks that accept a dependencies array, make sure to leverage it and include only the minimal set of dependencies.
---
### Documentation/Versioned Docs/Version 1.X/Guides/Section List (documentation/versioned_docs/version-1.x/guides/section-list.md)
---
id: section-list
title: SectionList
---
React Native has a convenience component on top of `FlatList`, called [`SectionList`](https://reactnative.dev/docs/sectionlist). This component has some additional props:
- [`sections`](https://reactnative.dev/docs/sectionlist#requiredsections)
- [`renderSectionFooter`](https://reactnative.dev/docs/sectionlist#rendersectionfooter)
- [`renderSectionHeader`](https://reactnative.dev/docs/sectionlist#rendersectionheader)
- [`SectionSeparatorComponent`](https://reactnative.dev/docs/sectionlist#sectionseparatorcomponent)
- [`stickySectionHeadersEnabled`](https://reactnative.dev/docs/sectionlist#stickysectionheadersenabled)
**`FlashList` offers none of these props but all of them are replaceable with existing props.**
The difficulty of migrating from `SectionList` to `FlashList` will depend on the data you have at hand - the data may be more suitable for `SectionList`, requiring you to massage the data, but the opposite can be true as well. In that case, using `FlashList` instead of `SectionList` might even result in less code.
Let's go through how to migrate from `SectionList` to `FlashList` in the following example - a contacts list.
This is how we could write such a list with `SectionList`:
```tsx
import React from "react";
import { SectionList, StyleSheet, Text } from "react-native";
interface Contact {
firstName: string;
lastName: string;
}
interface Section {
title: string;
data: Contact[];
}
const contacts: Section[] = [
{ title: "A", data: [{ firstName: "John", lastName: "Aaron" }] },
{
title: "D",
data: [
{ firstName: "John", lastName: "Doe" },
{ firstName: "Mary", lastName: "Dianne" },
],
},
];
const ContactsSectionList = () => {
return (
{
return {item.firstName};
}}
renderSectionHeader={({ section: { title } }) => (
{title}
)}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 32,
backgroundColor: "#fff",
},
});
```
To migrate to `FlashList`, we'd need to first convert the `contacts` variable to the following:
```tsx
const contacts: (string | Contact)[] = [
"A",
{ firstName: "John", lastName: "Aaron" },
"D",
{ firstName: "John", lastName: "Doe" },
{ firstName: "Mary", lastName: "Dianne" },
];
```
As you can see, you can add the section item right along with the data. Then in the `renderItem`, you can distinguish what to render based on the type of the item:
```tsx
const ContactsFlashList = () => {
return (
{
if (typeof item === "string") {
// Rendering header
return {item};
} else {
// Render item
return {item.firstName};
}
}}
getItemType={(item) => {
// To achieve better performance, specify the type based on the item
return typeof item === "string" ? "sectionHeader" : "row";
}}
estimatedItemSize={100}
/>
);
};
```
You can follow a similar pattern as for `renderItem` for the rest of the `SectionList`'s props.
If you want your section headers to be sticky, you will also need to compute the array for `stickyHeaderIndices`:
```tsx
const stickyHeaderIndices = contacts
.map((item, index) => {
if (typeof item === "string") {
return index;
} else {
return null;
}
})
.filter((item) => item !== null) as number[];
```
And that's it! Below you can find the whole example for `FlashList`:
```tsx
import React from "react";
import { StyleSheet, Text } from "react-native";
import { FlashList } from "@shopify/flash-list";
interface Contact {
firstName: string;
lastName: string;
}
const contacts: (string | Contact)[] = [
"A",
{ firstName: "John", lastName: "Aaron" },
"D",
{ firstName: "John", lastName: "Doe" },
{ firstName: "Mary", lastName: "Dianne" },
];
const stickyHeaderIndices = contacts
.map((item, index) => {
if (typeof item === "string") {
return index;
} else {
return null;
}
})
.filter((item) => item !== null) as number[];
const ContactsFlashList = () => {
return (
{
if (typeof item === "string") {
// Rendering header
return {item};
} else {
// Render item
return {item.firstName};
}
}}
stickyHeaderIndices={stickyHeaderIndices}
getItemType={(item) => {
// To achieve better performance, specify the type based on the item
return typeof item === "string" ? "sectionHeader" : "row";
}}
estimatedItemSize={100}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 32,
backgroundColor: "#fff",
},
});
```
---
### Documentation/Versioned Docs/Version 1.X/Guides/Testing (documentation/versioned_docs/version-1.x/guides/testing.md)
---
id: testing
title: Testing with Jest
slug: /testing
---
Since `FlashList` does not immediately render but waits for the size of the underlying `ScrollView` (unless you specify [`estimatedListSize`](usage#estimatedlistsize)), we need to mock triggering `onLayout` event.
## Setup
Add the following line to your `jest-setup.js` file:
```js
require("@shopify/flash-list/jestSetup");
```
To be sure, check if your jest.config.js file contains:
```
...
preset: 'react-native',
setupFiles: ['./jest-setup.js'],
...
```
## Example
Here is an example of using [`@testing-library/react-native`](https://callstack.github.io/react-native-testing-library/):
```tsx
import React from "react";
import { render } from "@testing-library/react-native";
describe("MyFlashListComponent", () => {
it("renders items", () => {
const { getByText } = render();
const element = getByText("Title of one of the items");
// Do something with element ...
});
});
```
---
### Documentation/Versioned Sidebars/Version 1.X Sidebars.Json (documentation/versioned_sidebars/version-1.x-sidebars.json)
{
"autoSidebar": [
{
"type": "autogenerated",
"dirName": "."
}
]
}
---
### Fixture/React Native/Package.Json (fixture/react-native/package.json)
{
"name": "fixture",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"build": "tsc -b",
"e2e:test:ios": "detox test -c ios.sim.release --artifacts-location /tmp/detox_artifacts/",
"e2e:build:ios": "detox build -c ios.sim.release",
"e2e:test:android": "detox test -c android.emu.release --artifacts-location /tmp/detox_artifacts/",
"e2e:build:android": "detox build -c android.emu.release",
"postinstall": "patch-package"
},
"dependencies": {
"@d11/react-native-fast-image": "^8.13.0",
"@react-navigation/native": "^7.1.31",
"@react-navigation/stack": "^7.8.2",
"@shopify/flash-list": "link:../../",
"react": "19.2.3",
"react-native": "0.84.1",
"react-native-gesture-handler": "^2.30.0",
"react-native-reanimated": "^4.2.2",
"react-native-safe-area-context": "^5.7.0",
"react-native-screens": "^4.24.0",
"react-native-worklets": "^0.7.4"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/runtime": "^7.25.0",
"@react-native-community/cli": "20.1.2",
"@react-native-community/cli-platform-android": "20.1.2",
"@react-native-community/cli-platform-ios": "20.1.2",
"@react-native/babel-preset": "0.84.1",
"@react-native/eslint-config": "0.84.1",
"@react-native/metro-config": "0.84.1",
"@react-native/typescript-config": "0.84.1",
"@types/jest": "^29.5.13",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.5",
"@types/react": "^19.0.0",
"babel-jest": "^29.2.1",
"babel-plugin-module-resolver": "^5.0.2",
"detox": "^20.44.0",
"glob": "^11.0.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "^0.77.0",
"patch-package": "^8.0.0",
"pixelmatch": "^5.3.0",
"pngjs": "^7.0.0",
"postinstall-postinstall": "^2.1.0",
"typescript": "5.8.3"
},
"jest": {
"preset": "react-native"
}
}
---
### Fixture/React Native/Tsconfig.Json (fixture/react-native/tsconfig.json)
{
"extends": "../../shared/tsconfig.base.json",
"references": [{ "path": "../../" }],
"include": ["src/**/*", "e2e/**/*"]
}
---