July 19, 2026
I get asked to look at slow React Native apps fairly often. The diagnosis is boringly consistent. In order of how often I find it:
FlatList is fast. FlatList with an inline renderItem arrow function that closes over state is not, because every parent render produces a new function identity and every row re-renders.
// Every keystroke in the search box re-renders 400 rows.
<FlatList data={items} renderItem={({ item }) => <Row item={item} onPress={() => select(item.id)} />} />
Hoist renderItem, memoise Row, and pass a stable keyExtractor. Pass the id to a stable callback rather than creating a closure per row. This one fix has accounted for more perceived speed than every other item on this list combined.
A single context holding the whole app state means any change re-renders every consumer. Contexts are not a state manager; they are a delivery mechanism. Split them by update frequency — the thing that changes on every keystroke should not share a provider with the thing that changes twice a session.
If an animation is driven by setState or by Animated without useNativeDriver, it runs on the JS thread and competes with everything else you are doing. Reanimated's worklets run on the UI thread and keep animating even while JS is busy.
The tell is an animation that stutters exactly when data arrives. That is not the animation's fault.
A 3000px JPEG rendered into a 120px avatar decodes at full size before it is scaled. Multiply by a scrolling list and you have a memory problem that presents as jank. Resize server-side, or use a CDN that takes dimensions in the URL.
Everything imported at module scope runs before the first frame. Analytics SDKs, font loading, a require of a 2MB JSON file — all of it lands between the splash screen and the first paint. Defer what is not needed to render the first screen.
Turn on the performance monitor and watch the two frame rates separately. UI dropping while JS holds means a rendering or image problem. JS dropping means you are blocking the JavaScript thread, and the cause is items 1, 2 or 5.
Guessing at this is how people end up rewriting in another framework and shipping the same bug.
If you have an app that got slow and you would rather not spend a fortnight bisecting it, that is a thing I do.