Understanding the extraData Prop in React Native's FlatList (And Why Your List is Being So Extra)
Okay, so real talk — if you’ve ever built a list in React Native and wondered why your UI is not updating even though your state clearly changed, you’re not alone. Like, been there, debugged that, cried a little. The culprit? You probably forgot about extraData. No worries though, we’re going to break this down so it actually makes sense.
Introduction
FlatList is lowkey one of the most powerful components in React Native. It handles large lists efficiently, only renders what’s visible on screen, and generally just gets it. But here’s the tea — FlatList is a PureComponent, which means it does a shallow comparison of its props to decide whether it needs to re-render.
That sounds great for performance, right? And it is! Until it isn’t.
The problem hits when you have some external state — like a list of selected items or a toggle — that affects how your list items look, but FlatList has no idea that state even changed. So it just… sits there. Unbothered. Rendering the same stale UI while you’re over here losing your mind.
That’s exactly where extraData comes in to save the day.
How FlatList Rendering Works
FlatList is built for performance. It only re-renders items that have actually changed, which is super efficient for long lists. But because it’s a PureComponent, it compares props shallowly — meaning it checks if the reference to a prop changed, not necessarily the deep values inside it.
So if you update a Set of selected item IDs in your component’s state, your component re-renders, but FlatList looks at its own props, sees nothing it cares about has changed, and goes “nah, I’m good.” It doesn’t re-render its items, and your UI looks stuck.
This is not a bug. This is a feature working as intended. But it can feel like a bug if you don’t know about extraData, and honestly the struggle is very real.
What is extraData?
Here’s the lowdown: extraData is a prop you pass to FlatList that essentially says “hey, watch this value — if it changes, please re-render.”
It’s not your list data. It’s not magic. It’s just a marker — a signal to FlatList that something relevant has changed and it needs to get off the couch and do a re-render.
From the React Native docs, extraData is described as:
A marker property for telling the list to re-render (since it implements PureComponent). If any of your
renderItem, Header, Footer, etc. functions depend on anything outside of thedataprop, stick it here and treat it immutably.
The key phrase there? Treat it immutably. FlatList checks if the reference to extraData changed. So if you pass the same object reference, even if the contents changed, FlatList won’t notice. You need to pass a new reference — like a new object, a primitive that updated, or a serialized string.
Common Use Cases
So when do you actually need extraData? Honestly, more often than you’d think. Here are the scenarios that come up all the time:
-
Multi-select lists — You’re building a list where users can select multiple items (think a contacts picker or a to-do list). Your selected state lives outside of
data, so FlatList won’t know to update the checkmarks withoutextraData. -
Like/Unlike or Toggle states — You’ve got a feed where users can like posts. The liked state lives in a
SetorMapoutside of the list data. Same problem, same fix. -
Expand/Collapse items — Accordion-style list items that open and close based on external state? Yep,
extraDatato the rescue. -
Loading or error states — Sometimes items render differently based on a loading spinner or an error badge. If that state lives outside
data, pass it in viaextraData.
Practical Code Example
Let’s get into it with some actual code. Here’s a FlatList that lets users select items — first the broken version, then the glow-up.
❌ Broken — No extraData
import React, { useState } from "react"
import { FlatList, Text, TouchableOpacity, View } from "react-native"
const DATA = [
{ id: "1", title: "Morning Coffee ☕" },
{ id: "2", title: "Avocado Toast 🥑" },
{ id: "3", title: "Yoga Session 🧘" },
]
export default function MyList() {
const [selectedIds, setSelectedIds] = useState(new Set())
const toggleItem = id => {
setSelectedIds(prev => {
const updated = new Set(prev)
updated.has(id) ? updated.delete(id) : updated.add(id)
return updated
})
}
const renderItem = ({ item }) => {
const isSelected = selectedIds.has(item.id)
return (
<TouchableOpacity
onPress={() => toggleItem(item.id)}
style={{ backgroundColor: isSelected ? "#d0f0c0" : "#fff", padding: 16 }}
>
<Text>{item.title}</Text>
</TouchableOpacity>
)
}
return (
<FlatList
data={DATA}
keyExtractor={item => item.id}
renderItem={renderItem}
// ❌ No extraData — FlatList has no idea selectedIds changed
/>
)
}
Tap an item. Nothing happens visually. Your state is updating (you can verify with a console.log), but FlatList is completely unbothered. This is the vibe we do NOT want.
✅ Fixed — With extraData
import React, { useState } from "react"
import { FlatList, Text, TouchableOpacity, View } from "react-native"
const DATA = [
{ id: "1", title: "Morning Coffee ☕" },
{ id: "2", title: "Avocado Toast 🥑" },
{ id: "3", title: "Yoga Session 🧘" },
]
export default function MyList() {
const [selectedIds, setSelectedIds] = useState(new Set())
const toggleItem = id => {
setSelectedIds(prev => {
const updated = new Set(prev)
updated.has(id) ? updated.delete(id) : updated.add(id)
return updated
})
}
const renderItem = ({ item }) => {
const isSelected = selectedIds.has(item.id)
return (
<TouchableOpacity
onPress={() => toggleItem(item.id)}
style={{ backgroundColor: isSelected ? "#d0f0c0" : "#fff", padding: 16 }}
>
<Text>
{item.title} {isSelected ? "✅" : ""}
</Text>
</TouchableOpacity>
)
}
return (
<FlatList
data={DATA}
keyExtractor={item => item.id}
renderItem={renderItem}
extraData={selectedIds} // ✅ FlatList now knows to re-render when this changes
/>
)
}
Now tap an item and boom — the green background and checkmark show up instantly. Because selectedIds is a new Set reference every time it updates (thanks to our functional state update), FlatList picks up the change via extraData and re-renders. We love to see it.
Common Mistakes
Okay, let’s talk about the traps people fall into, because some of these are genuinely sneaky.
🚩 Passing a new object literal every render
// DON'T do this
<FlatList extraData={{}} ... />
Every render creates a new {} object with a new reference, so FlatList re-renders on every single render of your parent component. That defeats the whole point of FlatList’s optimization. It’s the equivalent of turning off airplane mode to get notifications, but leaving it on all the time — counterproductive.
🚩 Only passing one piece of state when multiple affect your items
If your items change appearance based on both a selectedIds state and a loadingIds state, passing only one of them means FlatList might miss updates from the other. A clean pattern here is to combine them:
<FlatList extraData={[selectedIds, loadingIds]} ... />
Or serialize them into a string if you want to be extra safe about reference equality.
🚩 Confusing extraData with the data prop
data is your actual array of items. extraData is metadata about the render context. They’re not the same thing, and you shouldn’t be putting your list array into extraData. Keep them separate and purposeful.
Performance Considerations
Alright, let’s talk about keeping things actually performant while using extraData, because there’s a spectrum here.
Using extraData too broadly — If you pass a huge, frequently-changing object as extraData, you’re triggering re-renders more than necessary. Try to pass only what’s actually relevant to how list items render.
Using extraData too narrowly — If you forget to include all the external state that affects rendering, you’ll end up with the original stale UI problem. Balance is key.
Pair it with React.memo on item components — Even with extraData, if your item component isn’t memoized, it might re-render more than needed. Wrapping your renderItem component in React.memo means it’ll only re-render if its own props actually changed.
const ListItem = React.memo(({ item, isSelected, onPress }) => (
<TouchableOpacity
onPress={onPress}
style={{ backgroundColor: isSelected ? "#d0f0c0" : "#fff", padding: 16 }}
>
<Text>
{item.title} {isSelected ? "✅" : ""}
</Text>
</TouchableOpacity>
))
Know when to lift state — Sometimes the cleanest solution is restructuring your state so the selected/active status lives inside your data array (e.g. item.isSelected = true). If you can do that, you might not need extraData at all because changing data itself will trigger a re-render. It depends on your architecture, but it’s worth considering.
Conclusion
Okay, so here’s the TL;DR: extraData is a simple but super important escape hatch for FlatList’s pure component behavior. When your list items depend on state that lives outside of your data array, extraData is how you tell FlatList to pay attention.
Quick best practices recap:
- Always pass
extraDatawhenrenderItemdepends on external state - Make sure the value you pass changes reference when the relevant state changes
- Don’t pass a new object literal inline — that’s a performance trap
- Combine multiple state values if more than one affects item rendering
- Pair with
React.memoon item components for maximum optimization energy
It’s one of those props that’s easy to overlook until you hit the bug, and then you can never unsee it. Now that you know, you’ll be shipping buttery smooth, correctly-updating lists like the React Native dev you truly are.
For the full official docs, check out the React Native FlatList documentation