Reputation: 5103
I'm kinda new to React Native and have been having this issue with FlatList that I want to update the data source it uses and get it to update itself. My case is having a list of items that I want to show with FlatList and then each item is selectable. Most of the examples I've seen are using component and suggestion has been adding state
to extraData
to solve this issue. However, I'm using pure component and trying to see if there's any solution in this case. Following a sample project I've created. In this example, the press happens, data
is updated, but FlatList doesn't reflect the data
changes.
import { findIndex } from 'lodash'
import React, { useState } from 'react'
import {
SafeAreaView,
StyleSheet,
View,
Text,
StatusBar,
FlatList,
Pressable,
} from 'react-native'
import {
Colors,
} from 'react-native/Libraries/NewAppScreen'
var array = require('lodash/array')
const App = () => {
const [data, setData] = useState([
{
name: "Foo",
id: '1',
isSelected: false
},
{
name: "Boo",
id: '2',
isSelected: false
},
{
name: "Koo",
id: '3',
isSelected: false
},
{
name: "Poo",
id: '4',
isSelected: false
},
{
name: "Too",
id: '5',
isSelected: false
},
{
name: "Qoo",
id: '6',
isSelected: false
},
])
const tappedItem = (item) => {
const itemIndex = data.findIndex((i) => i.id == item.id)
const newItem = data[itemIndex]
newItem.isSelected = !newItem.isSelected
data[itemIndex] = newItem
setData(data)
}
const renderItem = (item) => {
return (
<Pressable onPress={() => tappedItem(item.item)}>
<View style={[styles.sectionContainer, { backgroundColor: item.item.isSelected ? 'pink' : 'lightgray' }]}>
<Text style={styles.sectionTitle}>{item.item.name}</Text>
</View>
</Pressable>
)
}
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<View style={styles.body}>
<FlatList
data={data}
keyExtractor={item => item.id}
renderItem={renderItem}
extraData={data}
/>
</View>
</SafeAreaView>
</>
)
}
const styles = StyleSheet.create({
body: {
backgroundColor: Colors.white,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
},
})
export default App
Upvotes: 1
Views: 1179
Reputation: 10645
Final Output:
Here is how you can implement the selection and deselection of FlatList items:
import React, { useState } from 'react';
import {
SafeAreaView,
StyleSheet,
View,
Text,
StatusBar,
FlatList,
Pressable,
} from 'react-native';
const App = () => {
const [data, setData] = useState([
{
name: 'Foo',
id: '1',
isSelected: false,
},
{
name: 'Boo',
id: '2',
isSelected: false,
},
{
name: 'Koo',
id: '3',
isSelected: false,
},
{
name: 'Poo',
id: '4',
isSelected: false,
},
{
name: 'Too',
id: '5',
isSelected: false,
},
{
name: 'Qoo',
id: '6',
isSelected: false,
},
]);
const tappedItem = (item) => {
console.log(item);
const modifiedList= data.map((element) => {
if (element.id === item.id) {
return { ...element, isSelected: !element.isSelected };
}
return element;
});
setData(modifiedList);
};
const renderItem = (item) => {
return (
<Pressable onPress={() => tappedItem(item.item)}>
<View
style={[
styles.sectionContainer,
{ backgroundColor: item.item.isSelected ? 'pink' : 'lightgray' },
]}>
<Text style={styles.sectionTitle}>{item.item.name}</Text>
</View>
</Pressable>
);
};
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<View style={styles.body}>
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={renderItem}
extraData={data}
/>
</View>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
body: {
backgroundColor: 'white',
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: 'black',
},
});
export default App;
Working of tappedItem()
:
/*
Lets assume we pressed first item in the FlatList, the item that we will be passing
is: {
name: 'Foo',
id: '1',
isSelected: false,
}
After that is done most important and also probably the easiest part,
the modification of the existing state, i.e. toggling the selected property.
*/
const tappedItem = (item) => {
console.log(item);
/*
In the following map operation, we iterate through the existing state, i.e. data.
and when we find the matching id, 1 in the case of
=> {
name: 'Foo',
id: '1',
isSelected: false,
}
we return the mutated object where we change only the `isSelected` property
You can see this happening here:
=> if (element.id === item.id) {
return { ...element, isSelected: !element.isSelected };
}
in above if statement, we spread the `element` object, toggle the `isSelected` property and return it.
If the id does not matches then we can simply return the original `element` object without any changes.
So after that, we will get the modifiedList which will have the `isSelected` toggled as we intend to.
Then we simply set the data state with that of the newly modified list.
=> setData(modifiedList);
*/
const modifiedList= data.map((element) => {
if (element.id === item.id) {
return { ...element, isSelected: !element.isSelected };
}
return element;
});
setData(modifiedList);
};
/*
this variation of your existing tappedItem function will work fine too,
just be careful not to mutate the states directly as you did in the original case
=> data[itemIndex] = newItem [❌]
=> let temp = [...data]
temp[itemIndex] = newItem; [✔]
*/
const tappedItem = (item) => {
let temp = [...data] //<= create a copy of current state
const itemIndex = data.findIndex((i) => i.id == item.id);
const newItem = data[itemIndex];
newItem.isSelected = !newItem.isSelected;
temp[itemIndex] = newItem; //<= modify that copy
setData(temp); //<= use it to set the state.
};
Full Working demo: Expo Snack
Upvotes: 2