react-brain
Browse decisions
React & language foundations 3App architecture 11UI 13Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness benchnewsletterarchitecturechangelogmethodologyroadmap

entriesui · verified 2026-10-01 · react + react-native

Lists & virtualization

reviewedconfidence: hightrack: heldthis tier holds 83% on the public scorecard (6/11 graded · 0 overturned) →

related decisions: animation

cited by: styling · animation · calendars · native

re-verified 8× — 2026-10-08 · 2026-09-24 · 2026-09-21 · 2026-09-10 · 2026-09-01 · 2026-08-18 +2 · changelog

recommendation

RN: FlashList for large lists, FlatList otherwise, ScrollView only for <20 static items. Web: TanStack Virtual (react-virtual) or react-window — both current (3.14.13 / 2.3.3, verified vs npm 2026-09-24); pick on API taste.

  • huge web tables (100k+ rows) → HighTable
  • one list engine across RN + web → Legend List
  • new large RN list surface where maintainer continuity outweighs library maturity → Legend List (actively released; FlashList is on critical-fixes-only while Shopify seeks a steward)
  • old-architecture RN app → FlashList v1.x (pinned; v2 is New-Arch-only), else FlatList
  • text-heavy RN feeds where hundreds of flat labels mount at once → react-native-plain-text for those labels (renders straight to UILabel/TextView; one string, one style, New Architecture only, 0.9.x pre-1.0; its unified `Text` selector auto-falls-back to RN `<Text>` for nesting/mixed styles/press/selection, so you no longer have to audit each element by hand)
  • exact thresholds / perf → react-native-best-practices (owns the rules)

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
ScrollView/.mapfine <20 static items only
FlatListRN baseline virtualization
FlashListrecycling; large RN lists (v2 = New Architecture ONLY; old arch → pin v1.x). 2.3.2 (2026-06-10, verified vs npm 2026-09-24, not deprecated); Shopify is leaving React Native and only fixes compatibility-breaking issues while it seeks a long-term steward. See note: FLASHLIST STEWARDSHIP3.0M/wk · ships in 7/34
react-window / react-virtual / HighTableweb virtualization; HighTable handles billion-row tables (viewport-only DOM + scrollbar downscaling)8.0M/wk · ships in 5/34
Legend Listone virtualization engine for RN + web (v3 stable; 3.4.0, 2026-09-21, verified vs npm 2026-09-24); SectionList + chat/anchoring APIs; signal-driven container pool → very small re-renders735k/wk · ships in 2/34

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

Migration lines

version and deprecation lines this decision tracks — for YOUR repo's sequenced plan: the doctor or npx -y @heart-it/react-brain migrate .

@shopify/flash-list < 2.0.0 → FlashList v2 [upgrade · effort M]

v2 is a ground-up New-Architecture rewrite (recycling, progressive rendering, pre-paint correction) and is New-Arch-ONLY — on the old architecture stay pinned to v1.x until the RN ladder is done

shopify.engineering/flashlist-v2

@tanstack/react-table < 9.0.0 → TanStack Table v9 [upgrade · effort M]

v9 went stable 2026-08-04 and moves reactivity underneath the Table APIs — per-feature atoms behind a TableReactivityBindings contract, so grid re-renders track the state a cell actually uses instead of whole-table state; callers keep passing ordinary values and calling the same Table methods, so this is a version-line move rather than a rewrite

tanstack.com/blog/tanstack-table-v9-reactivity · registry.npmjs.org/@tanstack/react-table/latest

npm weekly downloads (from the corpus's last signals run): @shopify/flash-list 3.0M · @legendapp/list 735k · react-window 8.0M · @tanstack/react-virtual 31.7M · @tanstack/react-table 26.6M

Verified notes

A list's cost scales with the nodes it MOUNTS, not the length of its data, so every tool here renders only what is on or near the screen — by windowing (mount the viewport, unmount the rest: FlatList, react-window, react-virtual) or by recycling (re-bind a fixed pool of views: FlashList). On React Native, FlatList's windowing is the baseline and FlashList's recycling wins at scale, where mount/unmount churn dominates; ScrollView mounts everything and is correct only for a few static rows. On the web the same logic applies to DOM nodes. Legend List is the one engine that covers both platforms. Exact thresholds are owned by react-native-best-practices (RN-RULE-USE-FLASHLIST). FLASHLIST STEWARDSHIP (2026-09-21, verified vs Shopify's announcement + npm; npm re-checked 2026-09-24): Shopify is migrating its apps off React Native back to Swift/Kotlin. For FlashList — which the post puts at ~2M downloads/week and calls "the default way to render high-performance lists in React Native" — Shopify commits only to FIXING CRITICAL ISSUES THAT BREAK COMPATIBILITY, and says it is in discussions with several companies about long-term stewardship. No transfer has been announced. The package is NOT deprecated on npm and latest is 2.3.2, published 2026-06-10, so the last release already predates the announcement, consistent with maintenance-only → FlashList stays the RN large-list default (maintained-for-compatibility is not broken), but a team starting a large list surface now should price in that the maintainer is leaving; Legend List is the actively-developed RN + web alternative. The tripwire fires on an npm deprecation flag (the usual signal of a rename or handover); a steward being NAMED is not machine-observable, so re-read the announcement thread when this entry is next challenged. The same announcement archives Restyle (not an option here) and hands react-native-skia to its author under a new name — RB-E-ANIMATION owns that thread. PLAIN TEXT (facts verified vs the repo README + props guide; npm 0.9.0, 2026-09-25, verified 2026-10-01): react-native-plain-text is a cell-content micro-optimization, not a list engine. It bypasses RN's text layout pipeline by rendering to the platform's native label widget (UILabel / TextView) and buys that with a hard constraint: single string, single style, no nested <Text>, no press handling (wrap it in a Pressable) and no selection (listed as To Do). It requires the New Architecture and does not run in Expo Go → adopt it AFTER virtualization is right, and measure first. 0.9.0 adds a unified `Text` SELECTOR component: it renders `PlainText` for simple strings and falls back to RN's own `<Text>` for anything `PlainText` doesn't support (nesting, mixed styles), so existing code can adopt it without manually auditing which elements qualify — the per-element single-style/no-nesting constraint itself is unchanged. TANSTACK TABLE V9 (9.0.0 stable on npm 2026-08-04, verified vs the registry and the two TanStack v9 posts): REACTIVITY moved underneath the Table APIs. Instead of one table-state object that adapters wrap from the outside, feature state is split into atoms (pagination, row selection, column sizing, …) behind a shared `TableReactivityBindings` contract, so each of its 9 framework adapters plugs in native signals (Angular/Solid/Vue) or TanStack Store; selecting a row updates that checkbox and the selected-row counter without making every cell depend on whole-table state. Callers still pass ordinary values and call the same Table methods, so this is an upgrade, not a rewrite. MEMORY: v8 built every row/cell/header object with its own copy of every method, each with its own closure scope; v9 hangs those methods on shared prototypes cached per table. On the authors' Playwright/CDP benchmark that is up to ~90% less memory on large tables, moving the practical browser ceiling from ~1–1.5M rows (at ~4GB) to roughly 10–16M. Their own edges: small tables (10 rows × 8 cols, all features) use slightly MORE memory in v9 because of added memoization, and the refactor carried one breaking change → "huge web tables → HighTable" is now a call about DOM/UX at that size, not about Table running out of memory first. HISTORY: 2026-10-01 REOPENED: react-native-plain-text 0.9.0 adds a unified `Text` selector component (auto-falls back to RN `<Text>` for nesting/mixed styles) — adoption no longer needs a manual per-element audit. 2026-08-18 REOPENED: react-native-plain-text promoted from skip to a when-clause on its 2nd independent signal (TWiR #293, then RN Rewind #52) — the reopen condition recorded at the first skip. 2026-08-18 REOPENED: the TanStack Table v9 memory post, carried as a cap skip since React Status #486, was re-read and kept — it moves the HighTable threshold. 2026-08-04 GAP CLOSED: TanStack Table v9 went stable (the beta line had been a skip since #291).

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

How to Build the Fastest Apps: Break the Rules (App.js Conf 2026)Jay Meistrich — author of Legend List

Why Legend List is fast, from its author: it mounts a fixed pool of absolutely-positioned containers once and never re-renders that array again — instead it *signals* individual containers to re-render themselves at a new position/item as you scroll, and turns frequent size changes into animated-style updates rather than list re-renders. The generalizable lesson ('render once'): keep each render tiny by pushing it to the leaf that actually changed. Complements the peterp.me engine walkthrough with the same-source mechanism; announces v3.0 stable (RN + web). Vendor talk.

FlashList v2: a ground-up rewrite for React Native's New ArchitectureTalha Naqvi (Shopify Engineering)

The maintainer's architecture deep-dive on FlashList's New-Architecture rewrite — view recycling, progressive rendering, layout prediction, and pre-paint correction via synchronous measurement — explaining the recycling machinery from first principles, not just the API.

React windowing vs. component recyclingIskander Samatov (LogRocket)

Cleanly separates the two long-list strategies — windowing (render only the viewport, mount/unmount on scroll) vs recycling (reuse nodes by reassigning keys) — and when to escalate. Bridges web (react-window) and RN (FlashList).

What's actually happening inside Legend ListPeter Piekarczyk

Explains Legend List's virtualization formula — estimated item size, draw distance, container-pool ratio, asymmetric 1.5x/0.5x buffering. A concrete mental model for list recycling performance (complements the FlashList piece).

Virtual scrolling for billions of rows (techniques from HighTable)Sylvain Lesage

The five techniques behind HighTable — lazy slicing, the ~17M-px canvas-height ceiling with downscaled scrollbars, dual local/global scroll modes, decoupled axes. The web huge-table counterpart to the RN list pieces above (HighTable is a listed option here).

Reactivity in TanStack Table V9TanStack

The architecture rationale behind the v9 line this entry's note records: v8 kept one stable table instance and let each adapter connect it to a framework's update model from the outside, which makes any state change look like whole-table churn. V9 puts a reactive graph underneath instead — per-feature atoms plus a `TableReactivityBindings` contract each adapter fills with its framework's own primitives — so a row selection updates that row and the counter, not every cell. The generalizable lesson matches this entry's Legend List readings: the cost of a big grid is what you make re-render, so put the subscription grain at the leaf.

Sources

Depth (in-domain rules) is owned by the react-native-best-practices skill — this entry is selection breadth.

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About lists & virtualization in React & React Native

Diataxis: Explanation. This page builds understanding of why long lists need virtualization and which strategy each tool uses. It is not a tutorial, and the exact thresholds (when FlashList, item-size rules) are owned by react-native-best-practices. Read this for the why.

The one fact that organises everything: cost scales with mounted nodes, not data length

A list is cheap or expensive based on how many view/DOM nodes it mounts, not how many items the array holds. Render 10,000 rows naively and you create 10,000 nodes — layout, memory, and scroll all collapse. The whole field exists to break that coupling: render only what's on (or near) the screen. Two strategies do it, and every library here is one of them:

  • Windowing — mount only the items in the viewport (plus a small buffer); as you scroll, unmount what leaves and mount what enters. The node count stays ~constant regardless of data size. (FlatList, react-window, react-virtual.)
  • Recycling — keep a small fixed pool of views and re-bind them to new data as they scroll in/out, instead of unmounting and remounting. Cheaper still for big, uniform lists because it avoids mount/unmount churn. (FlashList.)

So the first question is never "which list lib" — it's "how many nodes will this mount, and is windowing enough or do I need recycling?"

The default, and why

React Native: FlashList for large lists, FlatList otherwise, ScrollView only for <~20 static items. Web: react-virtual / react-window.

The RN ladder is about matching strategy to scale. ScrollView mounts everything — fine for a handful of static rows, a foot-gun beyond that. FlatList adds windowing — the correct baseline for most lists. FlashList adds recycling — the win for large lists, where mount/unmount churn dominates (its v2 is a New-Architecture rewrite; see the reading). On the web, the data isn't native views but DOM nodes, and the same logic applies: react-window / react-virtual window the viewport.

The landscape, and when each one wins

ScrollView / .map() — no virtualization; every item mounts. Correct only for small, static lists (<~20). Using it for real data is the most common list mistake.

FlatList — RN's built-in windowing. The right default; tune-able, mature, and enough for the majority of lists.

FlashList — recycling for large RN lists. Wins when lists are long and rows are uniform enough to reuse; v2's ground-up New-Architecture rewrite handles recycling, progressive rendering, and layout prediction (reading). Reach for it at scale, not reflexively. Note that v2 runs only on the New Architecture — old-arch apps must pin v1.x (or stay on FlatList).

Stewardship (verified 2026-09-24). Shopify is leaving React Native and has put FlashList on critical-compatibility-fixes only while it looks for a long-term steward; the latest release (2.3.2) predates that announcement and npm carries no deprecation flag. FlashList stays the RN large-list default, but a team starting a large list surface where maintainer continuity matters more than maturity should pick Legend List, which ships actively on both platforms.

react-window / react-virtual / HighTable (web) — web windowing. react-window/react-virtual cover normal long lists; HighTable handles billion-row tables with tricks the others don't (working around the ~17M-px canvas-height ceiling with downscaled scrollbars, decoupled axes — reading). Reach for HighTable specifically for huge tabular data.

Legend List — one virtualization engine across RN + web (v3 adds SectionList + chat APIs). The pick when you want a single list mental model on both platforms.

Tradeoffs and failure modes to name out loud

  • ScrollView for real data. The cardinal sin: it mounts everything, so it's smooth at 15 rows and janks at 200. Match the tool to the count.
  • FlashList reflexively. Recycling shines for large, uniform lists; for short or wildly heterogeneous lists, FlatList's windowing is simpler and fine. Don't reach for recycling before you have the scale that needs it.
  • Fighting recycling with unstable items. Recycled rows assume reuse; per-row unstable state, unkeyed content, or wildly variable heights undercut the pool and reintroduce churn.
  • Ignoring the thresholds. "How many items before FlashList," item-size estimates, and render-perf rules are exact and owned by react-native-best-practices — this page gives the model, that skill gives the numbers.

How it interacts with the rest of the stack

  • Charts/graphics (RB-E-CHARTS). Huge-data rendering shares DNA — at extreme scale (charts, billion-row tables) the answer shifts from DOM/view virtualization toward canvas/GPU rendering.
  • Data (RB-E-DATA). Long lists usually mean paginated/infinite server data; the list's windowing pairs with the data layer's useInfiniteQuery-style fetching.
  • Perf depth (react-native-best-practices). Exact thresholds, item-layout, and the RN-RULE-USE-FLASHLIST guidance live there.

In one paragraph

A list's cost is the nodes it mounts, not the data length — so render only what's on screen, via windowing (mount the viewport, unmount the rest) or recycling (reuse a fixed view pool). On React Native: ScrollView only for tiny static lists, FlatList (windowing) as the baseline, FlashList (recycling) at scale. On the web: react-window/react-virtual for normal lists, HighTable for billion-row tables; Legend List when you want one engine across both. Don't use ScrollView for real data, don't reach for recycling before you need it, and get exact thresholds from react-native-best-practices.


See also: RB-E-CHARTS (canvas/GPU rendering at extreme data scale), RB-E-DATA (paginated / infinite data behind long lists). Exact thresholds + render-perf rules: the react-native-best-practices skill.

Related in ui: styling · component-libs · animation · a11y · i18n · charts · editors · svg · maps · calendars · sheets · polish