### README (README.md)
WebsiteDiscordGetting startedUsagePerformanceKnown Issues

**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