React Native apps do not get slow because of one catastrophic mistake. They get slow because a dozen small ones compound.
Across React Native codebases, recurring issues show up with striking consistency: heavy navigation patterns, oversized ScrollViews, inline functions passed as props, unoptimised images, and unnecessary re-renders. Each one is fixable in isolation. Together they explain why a snappy prototype turns into a janky release build on mid-range hardware.
The short version
- Default to React Navigation for new apps; legacy navigators belong in prototypes only.
- Replace ScrollView with FlatList for any list longer than a single screen.
- Avoid inline functions as props — they create unstable references.
- Resize and cache images before they hit the screen.
- Profile before you optimise.
Why these problems creep in
React Native's bridge architecture serialises messages between the JavaScript thread and the native thread. Every render, every prop change, every navigation transition crosses that bridge. Stack up dozens of unnecessary crossings per frame, and the UI thread falls behind. Animations stutter. Scrolling drops frames. Users notice.
The fixes that matter most
Navigation choices
React Navigation is the standard for a reason — actively maintained, cross-platform, and battle-tested. The older `Navigator` and `NavigatorIOS` components are limited and partially deprecated. For new projects, there is no reason to use anything else.
Stop using ScrollView for long lists
ScrollView renders every child at once, even items far off-screen. A list of 500 items means 500 component instances held in memory simultaneously. FlatList virtualises the list, mounting only what is visible. The performance gap is not subtle — for anything longer than a screen of content, FlatList is mandatory.
Do not pass inline functions as props
Writing `onPress={() => doSomething(id)}` looks harmless. The catch: every render creates a new function reference. Child components wrapped in `React.memo` or `PureComponent` will re-render because the prop technically changed, even if the underlying data did not. Pull the function outside the render path or wrap it with `useCallback`.
Optimise and cache images
Large images eat memory and slow initial renders. Resize to the dimensions you actually display. Consider libraries like `react-native-fast-image` for caching — it handles the memory-heavy lifting that the built-in Image component leaves on the table. Formats like WEBP can shrink file sizes by up to 75% compared to JPG or PNG at equivalent quality.
Watch for unnecessary re-renders
Use `React.memo()` to cache component output when props have not changed. Combined with stable function references, this prevents wasteful re-renders cascading down the component tree. The caveat: memoisation adds cognitive overhead. Profile first, then memo the components that actually re-render too often.
Our analysis
Most React Native performance problems are not exotic. They are the same handful of mistakes repeated across teams. That is actually good news — a checklist solves most of them.
Who benefits most?** Teams shipping production apps with long lists, image-heavy feeds, or frequent user interactions. **Who loses?** Prototype codebases that will never face real-world scale. Optimising for problems that do not exist wastes time you could spend building features.
The trade-offs worth keeping in mind: some fixes are pure wins. Switching from ScrollView to FlatList costs almost nothing and pays off immediately. Others, like aggressive memoisation, add complexity with diminishing returns on small component trees. Profile before you change code, and profile after to confirm the change helped.
What to watch:** The new React Native architecture (JSI and Fabric) removes the bridge entirely for many operations. Once it stabilises, projects still on the old bridge will see the biggest performance gains from an upgrade. Run the performance monitor on a real device — numbers beat gut feel every time.
Who should care
| Team type | Priority fixes | Why |
|---|---|---|
| E-commerce or product catalogue apps | FlatList, image optimisation | Long lists + image-heavy screens compound fast on low-end devices |
| Social feeds or content apps | FlatList, image caching, React.memo | Frequent scrolling, high re-render frequency |
| Dashboard or form-heavy apps | Stable function references, memo | Users interact rapidly; unnecessary renders feel sluggish |
| Prototypes and MVPs | React Navigation only | Optimisation is premature until you have real usage data |
A worked scenario
Your team has a product list screen that scrolls smoothly on Wi-Fi but stutters on mid-range Android over 4G. The list is a ScrollView with 200 product cards. Each card renders an inline thumbnail, an inline press handler, and full descriptions by default. Swap ScrollView for FlatList, move the handler to a stable callback, lazy-load images, and add `getItemLayout` for fixed-height cards. One pull request. Frame times drop into the comfortable zone.
What to watch next
Before shipping, run the React Native performance monitor and the Flipper plugin on a real device — not the simulator. Also keep an eye on the JSI and Fabric rollout. When the new architecture lands in a stable release, the bridge removal alone will reshape the performance baseline for every React Native app.
Key takeaways
- Default to React Navigation for new apps.
- FlatList over ScrollView for any list longer than a screen.
- Stabilise function references with `useCallback` to avoid wasteful re-renders.
- Resize and cache images before they hit the screen.
- Profile first; change code second.
- Monitor the new architecture rollout — it removes the bridge for many operations.
Frequently asked questions
What is the most common React Native performance issue?
Using ScrollView for long lists. FlatList virtualises content and only mounts visible items.
Does React Navigation slow apps down?
No. It is the actively maintained standard and the right default for most projects.
Why do inline functions hurt performance?
They create a new reference on every render, which causes memoised child components to re-render unnecessarily.
Should I memo every component?
Not blindly. Memoisation adds cognitive overhead. Profile first, then memo the components that actually re-render too often.
What is the new architecture?
A shift to JSI and Fabric that removes the bridge between JavaScript and native code, improving performance for many operations. **END**