React Native moves fast when your app is small. But once you start layering in features, media uploads, and scroll-heavy views, you quickly realize how easy it is to slow everything down.
Here are real lessons from scaling a React Native app that needed to stay smooth, even on mid-range devices. No fluff, just what worked.
1. FlatList Is Non-Negotiable
If you're rendering a list with more than 10 items and using ScrollView,
you're asking React Native to draw the entire thing at once. It's like trying to cook every meal of the week on Sunday night in one giant pot.
Use FlatList
. It's built for recycling items off-screen and reducing memory usage.
<FlatList
data={items}
keyExtractor={(item) =>item.id.toString()}
renderItem={({ item }) => <ListItemitem={item} />}
initialNumToRender={10}
windowSize={5}
/>
Set initialNumToRender
and windowSize
based on your screen size and content. The defaults are okay, but tuning them helps when you know what your users are doing.
2. Memoization Fixes Re-Renders That Kill Performance
If you don’t memoize list items, they’ll re-render every time a parent component updates. That’s like repainting every wall in your house just because you moved a chair.
tsx
CopyEdit
const ListItem =React.memo(({ item }) => {
return <Text>{item.name}</Text>;
});
Use React.memo
to wrap components that don’t need to update every time. Pair it with useCallback
and useMemo
to stabilize functions and objects passed down as props.
3. Lazy Load Screens and Expensive Components
We used to preload everything on app startup. Map views, videos, large modals. Our cold start time was awful.
Now, we only render heavy components when they’re needed.
{showVideo && <VideoPlayer />}
Or conditionally load a screen:
const LazyComponent= React.lazy(() => import('./HeavyScreen'));
It’s like not turning on all the lights in your house until someone actually walks into the room.
4. Use InteractionManager to Defer Work
React Native is single-threaded, and every JS task blocks the UI. If you’re doing something like logging analytics, parsing data, or fetching suggestions, wait until interactions are done.
useEffect(() => {
const task =InteractionManager.runAfterInteractions(() => {
// This happens after navigation finishes
});
return () => task.cancel();
}, []);
Keep animations and transitions snappy by getting out of the way.
5. Optimize Images Like Your App Depends On It
Because it does. Uncompressed images destroy memory, kill FPS, and cause layout jumps.
We used react-native-fast-image
for proper caching and priority-based loading.
import FastImagefrom 'react-native-fast-image';
<FastImage
style={{ width: 100, height: 100 }}
source={{ uri: imageUrl, priority:FastImage.priority.high }}
resizeMode={FastImage.resizeMode.cover}
/>
Also, compress images and videos before upload. We built native compression logic because off-the-shelf libraries weren’t reliable across both platforms.
6. Reanimated Makes Gestures and Animations Feel Native
The built-in Animated API is fine until you need fluid gestures or complex animations. Then it starts choking.
react-native-reanimated
runs on the UI thread, not JS, so it doesn't get blocked by renders or timers.
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
const offset =useSharedValue(0);
const animatedStyle= useAnimatedStyle(() => ({
transform: [{ translateY: offset.value }],
}));
<Animated.Viewstyle={animatedStyle} />
It takes more setup, but it feels native because it is.
7. Flatten Your Views
Every extra View adds layout calculation. Nesting 12 levels deep for styling reasons adds up fast.
Before:
<View>
<View>
<View>
<Text>Hello</Text>
</View>
</View>
</View>
After:
<View>
<Text>Hello</Text>
</View>
Use DevTools or Xcode’s Debug View Hierarchy to spot over-nesting.
8. Keep Your JS Bundle Clean
Your users download everything in your bundle. If you’re importing massive libraries you don’t use or leaving dead code in features you scrapped, you're bloating the app for no reason.
To check bundle size:
npx react-nativebundle \
--platform ios \
--entry-file index.js \
--bundle-output ./main.jsbundle \
--sourcemap-output ./main.jsbundle.map
npx source-map-explorer main.jsbundle main.jsbundle.map
We found an old calendar lib that was adding 800kb to our bundle. Deleted it. No regrets.
React Native doesn’t have a performance problem by default. Most performance issues come from letting things pile up without thinking about how it all runs under the hood.
Use FlatList
, memoize your components, lazy load your screens, compress your media, and don’t be afraid to rip out stuff that’s not pulling its weight. It’s not about making it perfect. It’s about making smart tradeoffs that let your app stay fast as it grows.
If you’re building in React Native and need help scaling performance or architecture, feel free to reach out. I’ve been there, and happy to share more than just the blog version.
- Written by Karly Lamm
Looking for Midwest-Based Developers for Your Business?
Open up new opportunities, grow revenue and improve user experience with Aviron Software. Based in St. Louis with USA programmers & QA specialists, Aviron can help your business whether on web, mobile apps or desktop software development. Get in touch via hello@avironsoftware.com or contact us.
Do you prefer to text? Send a text over to (314) 541-3446 to setup a time to discuss growing your business through software.