I have a react native project. Part of my react native project is a grid that I created. When a user clicks on an item in the grid, I want to update an array that I have set in state const [selectedHomeTypes, setSelectedHomeTypes] = useState(['Houses'])
.
When I add an item to the array, I want it to udpate the grid so that all the items in the array have a blue background. If there was 1 item in selectedHomeTypes
then 1 item in the grid has a blue background. If I add a second item to selectedHomeTypes
, I want two items in the grid to have a blue background and sale with when I unselect an item that is deleted from the array.
What is hapening now is that selectedHomeTypes
is being updated like normal so when an item that is not in the state is clicked on it is added to the array and same with unslelect. The issue is that the background colors of the items are not updating at all and no matter what happens, only the default item has blue background. Nothing is visually changing….
Main Screen:
const [selectedHomeTypes, setSelectedHomeTypes] = useState(['Houses'])
const updateSelectedHomeTypes = (selected) => {
let selectedHome = selectedHomeTypes
if(selectedHome.length == 0){
setSelectedHomeTypes(['Houses'])
}
if(selectedHome.includes(selected)){
let target = selectedHome.indexOf(selected)
selectedHome.splice(target, 1)
if(selectedHome.length == 0){
setSelectedHomeTypes(['Houses'])
} else {
setSelectedHomeTypes(selectedHome)
}
} else {
selectedHome.push(selected)
setSelectedHomeTypes(selectedHome)
}
}
return (
<View style={styles.filterContainer}>
<ScrollView style={styles.scrollContainer}>
<View style={styles.header}>
<Text style={styles.label}>
Filter
</Text>
<TouchableOpacity onPress={() => {resetFilter()}}>
<Text style={[styles.label, styles.blueText]}>
Reset
</Text>
</TouchableOpacity>
</View>
<View style={styles.sectionContainer}>
<FlatList
style={styles.homeTypeContainer}
data={homeTypeOptions1}
keyExtractor={(item) => item.key}
numColumns={numberOfColumns}
renderItem={(item) => {
return(
<GridItemComponent
item={item}
updateSelectedHomeTypes={updateSelectedHomeTypes}
selectedHomeTypes={selectedHomeTypes}
/>
)}}
/>
</View>
</ScrollView>
<TouchableOpacity onPress={() => {applyFilters()}}>
<View style={styles.buttonContainer}>
<Text style={styles.buttonText}>Apply Filters</Text>
</View>
</TouchableOpacity>
</View>
)
gridItemComponenet:
const GridItemComponent = (props) => {
const {
item,
updateSelectedHomeTypes,
selectedHomeTypes
} = props
return(
<>
{
selectedHomeTypes.includes(item.item.value) ? <TouchableOpacity style={styles.itemContainerBlue} onPress={() => {updateSelectedHomeTypes(item.item.value)}}>
<View style={styles.item}>
<Image style={styles.icon} source={item.item.image}/>
<Text style={styles.label}>{item.item.value}</Text>
</View>
</TouchableOpacity>
: <TouchableOpacity style={styles.itemContainer} onPress={() => {updateSelectedHomeTypes(item.item.value)}}>
<View style={styles.item}>
<Image style={styles.icon} source={item.item.image}/>
<Text style={styles.label}>{item.item.value}</Text>
</View>
</TouchableOpacity>
}
</>
)
}
list of property type options:
const numberOfColumns = 3
const homeTypeOptions1 = [
{
key: 1,
value: 'Houses',
image: require('./src/assets/home.png')
},
{
key: 2,
value: 'Condos',
image: require('./src/assets/building.png')
},
{
key: 3,
value: 'Lot/Land',
image: require('./src/assets/management.png')
},
{
key: 4,
value: 'Multi-family',
image: require('./src/assets/multi-family.png')
},
{
key: 5,
value: 'Manufactured',
image: require('.//src/assets/tiny-house.png')
},
{
key: 6,
value: 'Townhomes',
image: require('.//src/assets/townhouse.png')
}
]
as you can see in the image below, the selectedHomeTypes
has 3 items in the array but only 1 item is highlighted. I am having trouble trying to update the background color of selected items dynamically
———–UPDATE———————
how would I updated an array in useState
if I want to eliminate an item from the array and have it rerender once the item was eliminated from the array. Keep in mind I have the index of the iteam I want to eliminate.
const updateSelectedHomeTypes = (selected) => {
let selectedHome = selectedHomeTypes
if(selectedHome.length == 0){
setSelectedHomeTypes(['Houses'])
}
if(selectedHome.includes(selected)){
let target = selectedHome.indexOf(selected)
selectedHome.splice(target, 1)
setSelectedHomeTypes(selectedHome)
} else {
setSelectedHomeTypes([...selectedHome, selected])
}
}
2
Answers
At a glance I think this is the problem:
When you do this:
React doesn’t re-render because you’re setting
selectedHomeTypes
to the same array that’s already in state.You’ve pushed a new entry into it but React is doing an identity comparison between the old state and the new state to decide whether a rerender is needed. In this case
newArray === oldArray
so it doesn’t trigger an update.You could dodge this by spreading into a new array (and appending the new item instead of calling push):
When updating state using its previous value, use the callback argument (to avoid stale state values).
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
It should be
If you are updating a state property, using another state property, it’s best to use class components.
State updates to arrays should be done using methods that return a new array (
push
does not whileconcat
does).See this comprehensive article to learn how to update state arrays in React.