React Native Doctor
We built React Doctor to give React codebases a single health score. Today we're doing the same thing for mobile.
React Native Doctor scans your React Native and Expo projects and gives you a 0-100 score, plus the exact diagnostics behind it. It focuses on the three things mobile teams get bitten by most: performance, accessibility, and design.
Run your first scan
Point it at the root of a React Native or Expo project:
npx react-doctor@latestIt figures out your React Native version, Expo vs bare workflow, the JS engine (Hermes or JSC), and your navigation and state libraries before it scans. So the rules that run are the ones that actually apply to your app.
The three things that matter on mobile
Performance
Phones are slow and users notice. A dropped frame on the web is invisible; on a mid-range Android it's a visible stutter. The usual culprits are non-virtualized lists that mount every row at once, images with no caching that reload while you scroll, and work that runs on every render instead of once.
These are the issues that make an app feel cheap, so they're the first thing we scan for.
Accessibility
A huge share of accessibility bugs in React Native come from one thing: a touchable with no label. VoiceOver and TalkBack then announce "button" with no name, and the screen is unusable for anyone who can't see it.
Tap targets are the other big one. A 28x28 icon button is easy to miss for anyone, and fails the 44x44 minimum that both iOS and Android expect.
Design
Consistency and platform polish are what make an app feel built, not assembled. Shadow styles that silently don't render on Android, spacing that doesn't match your scale, and colors that skip your theme all read as "unfinished" even when nothing is broken.
What a scan looks like
React Native Doctor groups findings by concern and labels each one with a clear severity, so you always know what to fix first:
$ npx react-doctor@latest
React Native Doctor · Expo SDK 52 · Hermes · TypeScript · 38 files
Performance
warn app/feed/Feed.tsx:31 react-doctor/rn-no-scrollview-mapped-list
<ScrollView>{posts.map(...)}</ScrollView> builds every row up front, so
a long feed mounts hundreds of views and scrolling stutters. Use a
virtualized list like FlashList or FlatList.
warn app/feed/Avatar.tsx:3 react-doctor/rn-prefer-expo-image
Image from react-native has no caching, so avatars reload on every
scroll. Switch to <Image> from expo-image.
Accessibility
error app/feed/LikeButton.tsx:8 react-doctor/rn-touchable-missing-label
<TouchableOpacity> has no accessibilityLabel, so screen readers announce
"button" with no name. Add accessibilityLabel="Like".
warn app/feed/Row.tsx:42 react-doctor/rn-tap-target-too-small
Touch target is 28x28. iOS and Android both expect at least 44x44.
Increase the size or add hitSlop.
Design
warn app/feed/Row.tsx:19 react-doctor/rn-no-legacy-shadow-styles
shadowColor and shadowOffset don't render on Android. Use boxShadow so
the card shadow shows up on both platforms.
5 issues · 1 error · 4 warnings Score 74 / 100Errors vs warnings
Severity is the whole point of the score, so we keep it simple.
- error means it ships a real bug. Jank a user can feel, a control a screen reader can't use, a crash on Hermes. Fix these before you merge.
- warn means it's real but lower stakes. Polish, consistency, a small gap. Fix it when you can, or suppress it when the code is intentionally unusual.
How to fix one
Start with the performance finding that costs the most. This feed renders by
mapping an array inside a ScrollView:
<ScrollView>
{posts.map((post) => (
<Row key={post.id} post={post} />
))}
</ScrollView>A ScrollView has no virtualization. A 500-post feed builds 500 rows on the
first render and keeps every one mounted, on screen or not. On a mid-range
Android that is exactly where the scroll stutter comes from.
Switch to a virtualized list. It only mounts the rows on screen and recycles them as you scroll:
import { FlashList } from "@shopify/flash-list";
<FlashList
data={posts}
renderItem={({ item }) => <Row post={item} />}
keyExtractor={(item) => item.id}
/>;Now the feed mounts a handful of rows instead of hundreds, and scrolling stays smooth no matter how long the list gets. Every diagnostic comes with this kind of recommendation, so you're never staring at a rule name wondering what it wants.
Built for agents
We care a great deal about speed, and that includes the speed of fixing things. The output is deterministic and machine-readable, so you can wire it into CI and let a coding agent read the findings and fix them:
npx react-doctor@latest --jsonScan only what changed
When you're on a branch, scan just the diff:
npx react-doctor@latest --diff mainThis is just the start. We're expanding the rule set, adding more Expo-specific checks, and putting together a mobile leaderboard. Try it on your app and tell us what it finds.