Migration to v3

Version 3 introduces first‑class Web support and a new SectionList component, plus several API improvements.

Migrating from 3.6 to 3.7

New in 3.7 (3.7.0 is unreleased).

The only breaking type changes are new required members on the public ref and state types: code that implements or mocks these types must add them. Apps that only consume refs and state do not need to implement these members themselves. There are no deprecations in 3.7.

TypeNew required members
LegendListRef (React Native and Web)cancelScroll, scrollToKey, anchorVisibleContent
SectionListRefcancelScroll, scrollToKey, anchorVisibleContent, inherited from LegendListRef
LegendListStatedistanceFromEnd, isFollowingEnd

LegendListState.dataKey is a new optional member. listen accepts isFollowingEnd; distanceFromEnd is computed when getState() is called and is not a listen key. Existing 3.6-shaped listen implementations remain assignable. LegendListMetrics is unchanged: onMetricsChange still receives only headerSize and footerSize.

For example, update a test double that only needs to model cancellation and unreachable key navigation with these members. A mock that simulates actual scrolling should model the new completion behavior too.

import type { LegendListRef, LegendListState } from "@legendapp/list/react-native";

const newRefMembers: Pick<LegendListRef,
  "cancelScroll" | "scrollToKey" | "anchorVisibleContent"
> = {
  cancelScroll: () => {},
  scrollToKey: async () => {},
  anchorVisibleContent: () => undefined,
};

const newStateMembers: Pick<LegendListState,
  "distanceFromEnd" | "isFollowingEnd"
> = {
  distanceFromEnd: 0,
  isFollowingEnd: false,
};

Add those members to your existing mock's ref and state objects. See the ref methods and state fields for their contracts.

For SectionList key-based methods, pass flattened item keys of the form ${sectionKey}:item:${itemKey}, rather than the bare item key. sectionKey is the section's key or section-${sectionIndex} when omitted; itemKey comes from the section or list keyExtractor; without one, it uses item.key, then item.id, then the item index within the section, converted to a string. anchorVisibleContent accepts and returns those same flattened keys. See SectionList for header, footer, and separator keys.

Behavior also changes in 3.7:

  • Scroll promises now resolve on landing or settlement, including interruption, cancellation, replacement, or an unreachable target, instead of resolving early or never. Resolution does not guarantee arrival.
  • Use maintainScrollAtEnd.on.dataAppend with a stable keyExtractor for append-only following, leaving dataChange disabled. Without keyExtractor, any increase in data length, including a prepend, triggers following.
  • With a trailing inset, isAtEnd, onEndReached, and maintainScrollAtEnd now activate closer to the physical end because the inset is counted once.
  • Allow onStartReached / onEndReached to run after layout changes without a scroll event.
  • Pass external fixed-size dependencies through extraData so getFixedItemSize sizes are invalidated when they change.

See Chat interfaces and Scrolling a search match into view for examples.

✨ New in v3

Web Support

  • DOM‑native rendering (no React Native dependency required)
  • Same virtualization core as React Native
  • Web examples and docs

SectionList Component

  • @legendapp/list/section-list with a React Native‑compatible API
  • scrollToLocation support
  • Sticky section headers powered by stickyHeaderIndices

Always Render

  • alwaysRender keeps selected items mounted outside the virtualization window

Better Scroll & Metrics APIs

  • initialScrollAtEnd for chat and feeds
  • anchoredEndSpace for reserving readable space after a chat or feed anchor row
  • contentInsetEndAdjustment for floating composers and overlay UI at the end of a list
  • useKeyboardChatComposerInset for React Native keyboard chat composers
  • onMetricsChange for header/footer size changes
  • getState() now exposes listeners, element access, and scroll velocity

🔄 Breaking changes from v2

  1. Typed import paths

    • The root @legendapp/list component export has been removed in v3.
    • Use:
      • React Native: @legendapp/list/react-native
      • React (Web): @legendapp/list/react
  2. maintainVisibleContentPosition defaults

    • v3 now enables scroll-position stabilization on size changes by default
    • Default behavior is equivalent to maintainVisibleContentPosition={{ size: true, data: false }}
    • Toggle specific behavior as needed:
      • maintainVisibleContentPosition={{ data: true }} to anchor on data changes too
      • maintainVisibleContentPosition={{ size: false }} to disable size/layout stabilization
      • maintainVisibleContentPosition={true} to enable both size and data
      • maintainVisibleContentPosition={false} to disable both
  3. Size hints and fixed-size callbacks

    • getFixedItemSize is now (item, index, type)
    • getEstimatedItemSize has been removed. Prefer no estimate for most dynamic-size lists, estimatedItemSize only as a small initial allocation hint when rows are far from 100px, or getFixedItemSize when sizes are known exactly.
    • suggestEstimatedItemSize has been removed. Use ref.current?.getState().getAverageItemSizes() to inspect measured average sizes directly.
  4. Sticky headers prop rename

    • stickyIndices has been removed. Use stickyHeaderIndices.
  5. Keyboard integration entrypoint update

    • v2 keyboard docs used @legendapp/list/keyboard-controller and LegendList.
    • In v3 use @legendapp/list/keyboard and import KeyboardAwareLegendList.
    • LegendList is no longer exported from the @legendapp/list/keyboard entrypoint.
    • Chat screens can use KeyboardAwareLegendList, useKeyboardScrollToEnd, and useKeyboardChatComposerInset from @legendapp/list/keyboard.
    • KeyboardAvoidingLegendList is available from @legendapp/list/keyboard-legacy for apps that still need the previous keyboard-avoiding integration.
  6. renderItem is a render callback

    • renderItem is called directly as (props) => ReactNode.
    • Do not pass a hook-using item component directly as renderItem={Row}.
    • Return hook-using item components from the callback instead: renderItem={({ item }) => <Row item={item} />}.
  7. Imperative scroll methods are async

    • These ref methods now return Promise<void>:
      • scrollIndexIntoView
      • scrollItemIntoView
      • scrollToEnd
      • scrollToIndex
      • scrollToItem
      • scrollToOffset
    • If you run logic after a programmatic scroll, await the call. The promise also resolves if the scroll is cancelled or superseded, so resolution does not guarantee arrival at the target.
  8. getState() shape changed for advanced integrations

    • state.positions is no longer part of the public getState() return value.
    • Replace direct map access with:
      • positionAtIndex(index)
      • positionByKey(key)
    • getState() now also exposes listener helpers (listen, listenToPosition), scrollVelocity, and getAverageItemSizes().
  9. Removed compatibility props and types

    • initialContainerPoolRatio has been removed. LegendList manages spare container capacity automatically.
    • InitialScrollAnchor has been removed. Use ScrollIndexWithOffsetPosition for public initial-scroll position typing.

Migration checklist

  • Move imports to typed platform entrypoints (/react-native or /react)
  • Update fixed-size callback signatures to (item, index, type)
  • Replace getEstimatedItemSize with no estimate, estimatedItemSize only for unusual initial sizes, or getFixedItemSize
  • Replace suggestEstimatedItemSize with getState().getAverageItemSizes()
  • Replace stickyIndices with stickyHeaderIndices
  • Remove initialContainerPoolRatio; spare container capacity is automatic
  • Replace InitialScrollAnchor type references with ScrollIndexWithOffsetPosition
  • Update keyboard imports from @legendapp/list/keyboard-controller to @legendapp/list/keyboard and use KeyboardAwareLegendList
  • If you still need the previous keyboard-avoiding integration, import KeyboardAvoidingLegendList from @legendapp/list/keyboard-legacy
  • Wrap hook-using item components in a renderItem callback instead of passing them directly
  • For floating composers, replace content padding workarounds with contentInsetEndAdjustment on web or useKeyboardChatComposerInset with KeyboardAwareLegendList on React Native
  • await imperative scroll calls to wait for completion or cancellation
  • Update advanced getState() consumers to use positionAtIndex / positionByKey instead of positions

Install

npm install @legendapp/list

On this page