March 14, 2025
March 14, 2025
This post is a follow-up to Don't Re-Render All FlatList Items. In the previous article, we optimized FlatList to prevent unnecessary re-renders when updating list item values. This time, we’ll tackle another common issue: avoiding re-renders when adding or removing items.
Many developers believe they need to wrap every FlatList prop in useCallback
or useMemo
to optimize performance. But is that really necessary? Let’s find out.
This issue is especially common in apps with infinite scrolling or pagination, where new items are fetched and added to a list dynamically. If your app uses onEndReached
to fetch new pages from an API, the same principles apply.
Imagine an app that fetches data from the internet and appends new items to a FlatList. Here’s what we'd expect:
But in many cases, all items re-render when a new one is added!
Here’s an Expo Snack where all items re-render when we add a new one. You'll edit it yourself later!
An example app where items are added and removed.
How to test:
Here’s what happens in the console when adding items in the unoptimized version.
Example console output:
Rendering item: 1
Rendering item: 2
Rendering item: 1 <- Why is this rendering again?
Rendering item: 2
Rendering item: 3 <- Only this one is new
Rendering item: 1
Rendering item: 2
Rendering item: 3
When we add a new item to the list, we expect only that new item to render. However, FlatList ends up re-rendering all items. Why does this happen? The issue comes from how React handles state updates.
In our app, the FlatList’s data
prop is an array of ids
. We're working with immutable data, which means we don’t modify an existing array to add a new elements. Instead, we create a new array with the updated data. That means that whenever we add a new item, ids
gets a brand new array reference. Even though the contents of ids
are almost the same (except for the new item), FlatList sees a completely new array it should render.
ids
array that includes the new item.data
prop (even though most items are unchanged).This isn’t a bug — FlatList is simply reacting to the updated state as intended. Fortunately, we don’t need to change how FlatList works. Instead, we can stop unnecessary re-renders by making a small tweak in how we define our list items.
Here’s the best part: the fix is incredibly simple. Wrap ListItem
in React.memo
. That’s it.
Now, ListItem
will only re-render when its props change. Since it only takes an id
and they remain the same, old items won’t re-render when a new one is added.
Rendering item: 1
Rendering item: 2
Rendering item: 3 <- Only new items render now!
After the fix, only new items render as expected.
Boom! No more unnecessary re-renders. Now, only new items render when added while existing ones stay untouched. 🎉
FlatList
still calls renderItem
for each item, but React.memo
prevents actual re-renders unless the item’s props changed.
Let’s modify the example by adding a timer at the top of the screen that updates every second, showing the current time (hh:mm:ss
). This change will cause the entire component to re-render every second, which will also re-render every FlatList item unnecessarily.
const [time, setTime] = React.useState(new Date().toLocaleTimeString());React.useEffect(() => {const interval = setInterval(() => {setTime(new Date().toLocaleTimeString());}, 1000);return () => clearInterval(interval);}, []);
This makes all items re-render every second! 🚨
This happens because the parent component re-renders and creates a new instance of renderItem
. This means FlatList
receives a new prop and re-renders all items.
useCallback
To fix this, we need to wrap renderItem
and keyExtractor
in useCallback
. This ensures those functions stay the same (not just in terms of what they do, but also in terms of what they are) across renders, so no new unnecessary props.
keyExtractor={React.useCallback((id) => id, [])}renderItem={React.useCallback(({ item: id }) => <ListItem id={id} />, [])}
Alternatively, we can define them outside the component function or omit keyExtractor
, since id => id
is the default behavior.
A better approach is to extract the clock into its own component. This keeps state updates isolated, preventing unnecessary re-renders.
const Clock = () => {const [time, setTime] = React.useState(new Date().toLocaleTimeString());React.useEffect(() => {const interval = setInterval(() => {setTime(new Date().toLocaleTimeString());}, 1000);return () => clearInterval(interval);}, []);return <Text style={{ fontSize: 18, fontWeight: "bold" }}>Current time: {time}</Text>;};
By using <Clock />
inside ListScreen
, only the clock updates, keeping FlatList untouched. This is a better architectural choice because it:
FlashList takes a different approach to optimizing re-renders and some of these techniques might not be necessary. We won’t cover FlashList in this post, but it’s an alternative worth exploring. Let me know if you’d like a separate post about it!
We saw how FlatList re-renders all items when new ones are added even when it's not neceąry. By making a simple tweak (React.memo
), we prevent unnecessary re-renders and improve performance with minimal effort.
If you use FlatList in your app, I encourage you to test whether your list items are re-rendering more than necessary. A small optimization can make a huge difference in performance — especially on lower-end devices. 🚀
And if you missed it, check out part 1: Don't Re-Render All FlatList Items.
If you liked this post, why don't you subscribe for more content? If you're as old-school as we are, you can just grab the RSS feed of this blog. Or enroll to the course described below!
Alternatively, if audio's more your thing why don't you subscribe to our podcast! We're still figuring out what it's going to be, but already quite a few episodes are waiting for you to check them out.