skip to Main Content

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

3 items selected but 1 item with blue background

———–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


  1. At a glance I think this is the problem:

    When you do this:

    selectedHome.push(selected)
    setSelectedHomeTypes(selectedHome)
    

    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):

    setSelectedHomeTypes([...selectedHome, selected])
    
    Login or Signup to reply.
  2. 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

    this.setState(state=>
        ({selectedHomeTypes: state.selectedHomeTypes.concat(selected)})
    );
    

    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 while concat does).

    See this comprehensive article to learn how to update state arrays in React.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search