React Native

React Native apps — extends the React rules.

What this is

React Native apps. Its own rules are almost entirely about the two places mobile breaks in ways web doesn't: list performance (virtualization, memoized list items, hoisted inline props) and the JS-thread/UI-thread boundary (layout-property animation, blocking gesture handlers, Reanimated shared values touched outside worklets). Two rules exist purely because they crash on Android: bare strings outside <Text> and falsy && rendering.

Extends React (web) — those rules apply too; this file only adds what's specific to React Native.

How to onboard it

A repository picks up this stack by onboarding on a profile that includes it. redline init detects the profile from what is in the repository, so in most cases this is automatic:

terminal
$ npx redlinegate init  # detects the profile
$ npx redlinegate init --profile mobile-rn  # or name one

One profile pulls these rules in: mobile-rn. A profile that includes a stack extending this one gets these rules too.

Once onboarded, your files match this stack when they fit any of these globs:

  • **/*.tsx
  • **/*.native.ts
  • app/**/*.ts
  • src/screens/**
  • src/components/**
  • packages/**/src/**/*.tsx
  • apps/**/src/**/*.tsx

How to use it — 17 rules

Nothing to run. Once your profile includes React Native, redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.

Rule idSeverityCatches
react-native/unvirtualised-listBLOCKERLarge lists without virtualization. `ScrollView` + `.map()` over dynamic data, or `FlatList` where FlashList is the project standard — require FlashList (or FlatList minimum) for lists that can exceed ~20 items.
react-native/unmemoised-list-itemBLOCKERNon-memoized list item components — every list item must be `React.memo` with stable callback references (no inline arrow closures over changing values passed to items).
react-native/animating-layout-propertiesBLOCKERAnimating layout properties (`width`, `height`, `top`, `left`, `margin`) — animations must use `transform` and `opacity` only; layout properties run on the JS thread and jank.
react-native/bare-strings-outside-textBLOCKERBare strings outside `<Text>` — crashes on Android.
react-native/falsy-and-renderingBLOCKERFalsy `&&` rendering (`count && <X/>`) — renders `0`/`NaN` as text and crashes on Android; require ternary.
react-native/blocking-gesture-handlerBLOCKERBlocking the JS thread in gesture/scroll handlers — heavy work in `onScroll` without Reanimated worklets or throttling.
react-native/inline-props-in-list-itemsHIGHInline style objects / inline functions in list item props — hoist or memoize; breaks item memoization.
react-native/remote-image-componentHIGH`Image` from react-native for remote images where `expo-image` is available — no caching, no placeholder handling.
react-native/prefer-pressableHIGH`TouchableOpacity` in new code — use `Pressable`.
react-native/js-navigatorHIGHJS-based navigators where native stack (`@react-navigation/native-stack`) is available.
react-native/missing-safe-areaHIGHMissing safe-area handling in full-screen ScrollViews.
react-native/measure-over-onlayoutHIGH`measure()` calls where `onLayout` suffices.
react-native/native-dep-in-shared-packageHIGHNative dependencies added to shared monorepo packages instead of the app package.
react-native/reanimated-shared-value-misuseHIGHReanimated shared values read during render or mutated outside worklets/handlers.
react-native/intl-in-renderSUGGESTION`Intl.NumberFormat` / `Intl.DateTimeFormat` created inside render or loops — hoist to module scope.
react-native/missing-getitemtypeSUGGESTIONHeterogeneous lists without `getItemType` (FlashList).
react-native/derived-animation-in-renderSUGGESTIONDerived animation values computed in render instead of `useDerivedValue`.

Expected output

A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:

a finding from this file
Redline/BLOCKER [react-native/unvirtualised-list]: <one-line problem>

Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 17 rules here, 6 BLOCKER, 8 HIGH and 3 SUGGESTION. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.

If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.

How to edit it

Rules are edited in standards/stacks/react-native.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.

  1. Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
  2. node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
  3. node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
  4. Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
  5. Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
  6. node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.

Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.

The full file

standards/stacks/react-native.md · 30 lines · 2.5 KB
# React Native Review Rules

All React rules apply. Additionally:

## BLOCKER — request changes

- `react-native/unvirtualised-list` — **Large lists without virtualization.** `ScrollView` + `.map()` over dynamic data, or `FlatList` where FlashList is the project standard — require FlashList (or FlatList minimum) for lists that can exceed ~20 items.
- `react-native/unmemoised-list-item` — **Non-memoized list item components** — every list item must be `React.memo` with stable callback references (no inline arrow closures over changing values passed to items).
- `react-native/animating-layout-properties` — **Animating layout properties** (`width`, `height`, `top`, `left`, `margin`) — animations must use `transform` and `opacity` only; layout properties run on the JS thread and jank.
- `react-native/bare-strings-outside-text` — **Bare strings outside `<Text>`** — crashes on Android.
- `react-native/falsy-and-rendering` — **Falsy `&&` rendering** (`count && <X/>`) — renders `0`/`NaN` as text and crashes on Android; require ternary.
- `react-native/blocking-gesture-handler` — **Blocking the JS thread in gesture/scroll handlers** — heavy work in `onScroll` without Reanimated worklets or throttling.

## HIGH

- `react-native/inline-props-in-list-items` — Inline style objects / inline functions in list item props — hoist or memoize; breaks item memoization.
- `react-native/remote-image-component` — `Image` from react-native for remote images where `expo-image` is available — no caching, no placeholder handling.
- `react-native/prefer-pressable` — `TouchableOpacity` in new code — use `Pressable`.
- `react-native/js-navigator` — JS-based navigators where native stack (`@react-navigation/native-stack`) is available.
- `react-native/missing-safe-area` — Missing safe-area handling in full-screen ScrollViews.
- `react-native/measure-over-onlayout` — `measure()` calls where `onLayout` suffices.
- `react-native/native-dep-in-shared-package` — Native dependencies added to shared monorepo packages instead of the app package.
- `react-native/reanimated-shared-value-misuse` — Reanimated shared values read during render or mutated outside worklets/handlers.

## SUGGESTION

- `react-native/intl-in-render` — `Intl.NumberFormat` / `Intl.DateTimeFormat` created inside render or loops — hoist to module scope.
- `react-native/missing-getitemtype` — Heterogeneous lists without `getItemType` (FlashList).
- `react-native/derived-animation-in-render` — Derived animation values computed in render instead of `useDerivedValue`.