A janky app feels broken. Keep your UI at 60fps — optimize FlatList, avoid needless re-renders, run animations on the native thread, and profile to find the real bottleneck.
Why: smooth means 60 frames per second — about 16ms per frame. React Native runs your JS on one thread and the UI on another; jank happens when the JS thread is too busy to feed the UI thread in time. The fixes all come down to doing less work per frame and keeping animations off the JS thread.
60fps = ~16ms per frame budget
JS thread (your code, state) ──bridge──▶ UI thread (native rendering)
jank = JS thread too busy to deliver updates in time
fixes: render less, memoize, run animations with useNativeDriver/ReanimatedWhy: long lists are the most common performance trap. A few props help a lot: a stable keyExtractor, a memoized renderItem, and — when rows are a fixed height — getItemLayout, which lets FlatList skip measuring. Keep row components light and avoid inline functions/objects that change every render.
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem} // defined outside / memoized
getItemLayout={(_, index) => (
{ length: 64, offset: 64 * index, index } // fixed-height rows
)}
initialNumToRender={10}
windowSize={5}
/>Why: every state change re-renders the component and its children. Wrap pure child components in React.memo, memoize callbacks with useCallback, and derive values with useMemo so list rows do not re-render when unrelated state changes. This is the same React discipline — it just matters more on a phone.
import { memo, useCallback } from 'react'
const Row = memo(function Row({ item }: { item: Item }) {
return <Text>{item.name}</Text>
})
// In the parent: a stable function identity so Row's props don't change
const renderItem = useCallback(({ item }) => <Row item={item} />, [])Note: guessing wastes time — measure first. Toggle the in-app Performance Monitor (Dev Menu → Show Perf Monitor) to watch JS and UI frame rates live. Use the React DevTools Profiler to find components that re-render too often. Optimize the one thing the profiler points at, then measure again. Premature optimization just adds complexity.
1. reproduce the jank
2. Dev Menu → Show Perf Monitor (watch JS fps vs UI fps drop)
3. React DevTools Profiler (which component re-renders?)
4. fix that one thing → measure again