React Native Flow

← Retour au blog

March 12, 2026·10 min read

FlatList keys, extraData, and rerender traps

FlatList is fast until it isn’t—usually when keys change every render or the list thinks nothing changed when it should have. A few habits keep lists boring in a good way: stable identity for rows, explicit dependencies when state outside the data array matters, and pagination that does not fight reconciliation.

Keys are identity, not index

Using array index as `keyExtractor` is a trap the moment items reorder, insert, or come from pagination. Prefer server ids; if you must compose, make it stable across renders.

Duplicated keys silently wreck reconciliation; log in dev if you’re suspicious.

When the row doesn’t update

If `renderItem` closes over stale state, `extraData` is your friend. Pure memoized rows need their props to change when upstream data changes.

Don’t put huge inline objects in `renderItem`; they defeat memoization.

Pagination and flash

Append with stable ordering; debounce “load more” triggers. Empty and error states belong in the same component tree so users aren’t staring at a blank screen.

Measure with real device data volumes, not three placeholder rows.

Sanity checks before you ship a list screen

Scroll a long list on a mid-tier Android device with network throttling. Toggle selection state, pull-to-refresh, and “load more” in one session—watch for duplicate keys in the console. If rows contain images, verify decode size and that you are not mounting expensive subtrees on every small state change.

When something feels janky, profile JS thread vs UI thread; list issues often split cleanly once you know which one is pegged.

Identity and why keys are not cosmetic

Keys tell React which rows correspond to which domain entities across updates. When keys flutter—random values, indices on sorted lists—you pay with incorrect state retention, broken animations, and subtle selection bugs. Server-provided stable ids are ideal. If your backend uses composite keys, stringify them deterministically; avoid JSON.stringify of unordered objects. For optimistic UI, coordinate temporary ids with the eventual server ids and reconcile carefully when responses arrive. Duplicated keys are worse than missing keys in some cases because reconciliation silently merges state in confusing ways. Add dev-only assertions or logging when duplicates appear. Pagination adds complexity: ensure appended pages do not reshuffle earlier keys unless data truly reordered. If it does, scroll position and selection state need explicit reset logic.

Memoization, pure rows, and prop stability

`React.memo` on row components helps only if props are referentially stable. Inline style objects and arrow functions in `renderItem` defeat memo unless you hoist them. Consider `useCallback` for handlers keyed by id, or curried factories outside render. `extraData` should include every external input that affects row appearance beyond `data`—selection sets, theme tokens, locale—but avoid passing entire app state. Split heavy subtrees into child components with narrower props. Images in rows need explicit sizing and caching discipline; decoding large bitmaps on the JS thread still hurts even if reconciliation is fine. When using context, remember all consumers rerender on context change—sometimes lifting selection state to a row-local store or using Zustand selectors reduces churn.

Pagination, flash lists, and loading skeletons

Infinite scroll should debounce ‘load more’ triggers and avoid duplicate fetches when users fling. Show footers for loading and error with retry; empty states deserve first-class design. If you switch between FlashList and FlatList, revalidate `estimatedItemSize` and recycling assumptions—wrong estimates cause jitter. For bidirectional feeds, ensure prepend operations preserve scroll anchors; users hate losing their place. Test with slow networks and large datasets on low-RAM Android devices—emulators hide memory pressure. Consider windowing thresholds: `initialNumToRender`, `windowSize`, `maxToRenderPerBatch` tuned for your row height distribution. Measure rather than guess.

Gestures inside rows and nested scrollables

Swipeable rows, nested horizontal carousels, and drag-and-drop complicate touch handling. Ensure gesture handlers do not fight the parent scroll view; native driver settings matter. Test on Android with different pointer velocities. If rows expand/collapse, animate layout carefully and preserve keys through toggles. Nested virtualized lists are generally discouraged; if unavoidable, isolate them with fixed heights or explicit `nestedScrollEnabled` patterns per platform docs. Document any platform-specific hacks—future you will thank present you.

Debugging list jank systematically

Start by identifying whether JS or UI thread is saturated. Use the performance monitor, Systrace, or Flipper tooling appropriate to your stack. If JS, look for expensive `renderItem` work, unstable props, or synchronous storage reads. If UI, look at image decoding, shadows, and overdraw. Remove animations temporarily to isolate costs. Binary search recent commits if regression is sudden. Keep a ‘known good’ device in the office for comparisons—flagship phones mask issues. When you fix a bug, add a short performance test or at least a checklist item in QA scripts for long lists.

Accessibility and lists

Screen readers linearize lists; ensure meaningful row labels and hints for actions inside rows. Loading more should announce politely, not spam. When selection mode toggles, move focus predictably. For reorderable lists, expose position changes. Test with TalkBack and VoiceOver on hardware. Dynamic type scaling can change row heights—verify truncation and clipping. These concerns often overlap with performance: more layout passes hurt both.

Operational checklist before shipping list-heavy screens

Confirm keys stable across pagination, memoization effective, images bounded, empty/error states handled, load-more idempotent, performance validated on a mid device, accessibility reviewed, and analytics events for impressions do not fire excessively. Document known limitations—e.g., max items recommended—for support. After release, monitor scroll-related crashes and frame drops via instrumentation. Lists are core UX; they deserve the same rigor as checkout flows.

Shipping and reliability habits (1)

ScrollView versus FlatList is a data-volume question. Small static content belongs in ScrollView; long feeds belong in virtualized lists. Nested scrollables need explicit height contracts—redesign beats fighting physics. Document intentional choices so future refactors do not ‘optimize’ blindly.

Metro cache issues masquerade as logic bugs. Establish a documented reset ladder: dev server restart, cache flags, Watchman, derived data, then dependency reinstalls. Compare platforms when only one breaks—native steps diverge. Keep CI caches deterministic with lockfiles and pinned toolchains.

Platform differences worth rehearsing (2)

Performance work should start with measurement, not instinct. Watch JS thread versus UI thread separately; they bottleneck differently. Lists, images, and animations dominate most regressions—optimize those before micro-optimizing pure functions. Hermes, JSC, and bridge internals evolve; re-profile after every major upgrade instead of trusting last year’s numbers. Battery and thermal throttling on mid devices reveal issues flagship phones hide.

Security and privacy expectations move faster than roadmaps. Treat analytics, crash, and attribution SDKs as part of your threat model: initialize them deliberately, document data flows, and verify ‘off’ truly stops network calls. Client-side secrets are public secrets—anything shipped in an APK or IPA should be assumed extractable. Pair mobile changes with backend policies so authorization remains consistent across platforms.

Security, privacy, and data handling (3)

Deep links are a cross-team system: marketing URLs, hosted association files, entitlements, router params, and analytics query preservation. Debug with structured logging of raw URLs (scrub secrets) and reproduce cold-start races with auth hydration. Staging and production should be obviously separated—accidentally opening prod from a QA link erodes trust and pollutes data.

Push notifications walk a line between helpful and intrusive. Prime users with context, respect notification channels on Android, and measure opt-outs after campaigns—spikes mean copy or frequency problems. Payload design affects background behavior; test killed and locked-device states. Tokens belong server-side with rotation strategies; never treat the client as authoritative for subscription state.

Performance and measurement discipline (4)

E2E tests should protect revenue paths, not every permutation. Stable selectors (`testID`) beat text that marketing rewrites weekly. Flake management is a feature: quarantine, fix root causes, and keep smoke suites green on CI devices. Five reliable tests beat fifty flaky ones that everyone ignores.

OTA updates are powerful and risky: runtime compatibility, rollback plans, and user-visible behavior changes need governance. Channels should map to release maturity—staging versus production—with access controls on publish credentials. Large assets over cellular need care; silent failures erode trust more than a frank ‘update failed, retry’ message.

Team process and long-term maintenance (5)

JWT and session refresh flows need single-flight refresh, clear logout semantics, and secure storage for refresh tokens when appropriate. Parallel 401s should not stampede refresh endpoints. Clock skew and biometrics policies belong in explicit product decisions, not accidental implementation details.

In-app purchases require server validation, restore flows, and support tooling that respects privacy. Sandbox quirks are normal—budget QA time. Subscriptions interact with family sharing, regional pricing, and refunds; engineering must stay aligned with finance and legal narratives users see in receipts.

Shipping and reliability habits (6)

Analytics schema governance prevents warehouse disasters: version events, avoid high-cardinality strings, and align names across iOS, Android, and web. Consent gating must stop network calls, not just UI. Separate dev and prod projects to avoid polluting dashboards.

Testing onboarding changes with funnel metrics beats debating opinions. Segment by acquisition channel and platform; back behavior differs. Skip paths must be genuine—dark patterns may win short metrics and destroy brand trust. Localization length tests prevent clipped CTAs in verbose languages.

Platform differences worth rehearsing (7)

Environment variables should be classified: public-by-design, sensitive-with-mitigations, or never-on-device. `EXPO_PUBLIC_` values are extractable—treat them that way. Align env handling across EAS profiles and local dev; fail fast when keys are missing instead of shipping undefined behavior.

Project structure should make ownership obvious: routes as backbone, feature folders for product areas, thin screens, and shared infrastructure that is deliberately named. Refactor in vertical slices with device-tested releases—big-bang rewrites without tests are how teams lose weeks.

Security, privacy, and data handling (8)

Accessibility is compatibility. Labels, focus order, and dynamic type are not polish—they determine whether users can complete tasks at all. Test with VoiceOver and TalkBack on hardware; simulators miss focus bugs. When designs prioritize minimalism, negotiate text alternatives for icon-only controls. Accessibility regressions often follow navigation redesigns—add checklist items to those PRs specifically.

Internationalization is a product feature, not a string swap. Plural rules, RTL layout, and locale-aware formatting change behavior—not just copy length. Pseudolocale helps find clipping early, but real Arabic and German QA catches nuance. Avoid concatenating translated fragments; context matters. Document glossary terms so translators do not invent inconsistent product names.

Performance and measurement discipline (9)

Images and maps look simple in mocks and expensive in production. Decode sizes should match display sizes; static map thumbnails are not interchangeable with interactive MapView gestures. Quotas, API keys, and offline behavior need explicit fallbacks—addresses as text, retry buttons, and calm error copy. Monitor vendor dashboards for spikes that indicate bugs or abuse.

Error boundaries catch render failures, not native crashes or async mistakes. Pair them with platform crash reporting and structured client logs. Fallback UI should include build identifiers and humane copy—never raw stack traces for end users. Test fallbacks with screen readers; a broken error screen is still broken UX.

Team process and long-term maintenance (10)

Keyboard and form UX separate polished apps from ‘works on desktop simulators.’ Platform differences in soft input modes matter; test smallest phones and Android gesture navigation. Primary actions must remain reachable when the keyboard is visible—scroll containers and keyboard controllers exist because this problem is universal.

Type-safe navigation pays off when routes multiply. Keep param lists near navigators, validate external URLs, and avoid serializing non-JSON-safe values through params. Renaming routes is a cross-cutting change—update analytics, push payloads, and E2E selectors in the same release train.

Shipping and reliability habits (11)

Push notifications walk a line between helpful and intrusive. Prime users with context, respect notification channels on Android, and measure opt-outs after campaigns—spikes mean copy or frequency problems. Payload design affects background behavior; test killed and locked-device states. Tokens belong server-side with rotation strategies; never treat the client as authoritative for subscription state.

Images and maps look simple in mocks and expensive in production. Decode sizes should match display sizes; static map thumbnails are not interchangeable with interactive MapView gestures. Quotas, API keys, and offline behavior need explicit fallbacks—addresses as text, retry buttons, and calm error copy. Monitor vendor dashboards for spikes that indicate bugs or abuse.

Platform differences worth rehearsing (12)

OTA updates are powerful and risky: runtime compatibility, rollback plans, and user-visible behavior changes need governance. Channels should map to release maturity—staging versus production—with access controls on publish credentials. Large assets over cellular need care; silent failures erode trust more than a frank ‘update failed, retry’ message.

Keyboard and form UX separate polished apps from ‘works on desktop simulators.’ Platform differences in soft input modes matter; test smallest phones and Android gesture navigation. Primary actions must remain reachable when the keyboard is visible—scroll containers and keyboard controllers exist because this problem is universal.

Sponsorisé

Promo rapide