Search code examples
androidreact-nativepicker

React Native Picker: onValueChange triggers unintended onValueChange for other pickers


I am attempting to implement a selector page in which a user can select an indoor map based off 3 location attributes (facility, floor, suite). Any unique combination of these 3 attributes is guaranteed to have a unique indoor map. I am using React Native's Picker dropdown menu for the user to select which facility, floor, and suite they would like to view. Upon selection of any of these attributes, it should filter the options of the remaining attributes such that a valid combination exists.

The algorithm to filter the lists is not the problem for me. The problem is that when I select a picker value for one of the pickers, it resets the values for the other pickers to the defaultPlacholder value '---' and I cannot figure out why.

The selectedValue for each of the pickers is equivalent to the state variables that store the currently selected facility, floor, or suite ID. The state variables are initialized as 'defaultPlaceholder' in the beginning. Selecting a different ID from the dropdown menu calls setState to change the ID of the currently selected ID. At least, this is what I expected.

Instead, when setState(state variable) is called from onValueChange, instead of re-rendering once, the component re-renders MULTIPLE times (about 6-7). The state variable changes to the desired dropdown item for one render iteration and then automatically resets to the defaultPlaceholder value. Not only that, it somehow triggers the onValueChange for the other pickers, even though the state variables for those pickers did not change, only the list of picker items (e.g. pickerFacilitiesList) did. Now, the other picker items are reset back to the defaultPlaceholder value regardless of what the state values before were.

Even more baffling, this issue does not occur with the suites picker, only the facility and floors picker.

Any help would be greatly appreciated.

Below: a section of the render() function

    //.map iterates through uniqueFacilityIDs and generates a list of picker items
    const pickerFacilitiesList = uniqueFacilityIDs.map(a => {
      return <Picker.Item key={a.key} label={a.facilityName} value={a.facilityID} />;
    });    

    const pickerFloorsList = uniqueFloorIDs.map(a => {
      return <Picker.Item key={a.key} label={a.floorName} value={a.floorID} />;
    });

    const pickerSuitesList = uniqueSuiteIDs.map(a => {
      return <Picker.Item key={a.key} label={a.suiteName} value={a.suiteID} />;
    });


    if(this.state.pickerFacilityID === defaultPlaceholder || this.state.pickerFloorID === defaultPlaceholder || this.state.pickerSuiteID === defaultPlaceholder){
      return (
        <View>
          <Text style={{fontSize: 18, fontWeight: 'bold', textAlign: 'center', marginBottom: 25, marginTop: 15}}> Display Assets by Suites </Text> 

          <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30, marginBottom: 15, textDecorationLine: 'underline'}}>Filter suites by:</Text>

          <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30}}> Facility </Text>
          <Picker
            mode = 'dropdown'
            style = {{width: '80%', marginLeft: 30}}
            selectedValue={this.state.pickerFacilityID}
            onValueChange={(itemValue, itemIndex) => {
              this.setState({pickerFacilityID:itemValue})
            }}
          >
            <Picker.Item label = {defaultPlaceholder} value = {defaultPlaceholder}/>
            {pickerFacilitiesList}
          </Picker>  

          <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30}}> Floor </Text>
          <Picker
            mode = 'dropdown'
            style = {{width: '80%', marginLeft: 30}}
            selectedValue={this.state.pickerFloorID}
            onValueChange={(itemValue, itemIndex) => {
              this.setState({pickerFloorID:itemValue})
            }}
          >
            <Picker.Item label = {defaultPlaceholder} value = {defaultPlaceholder} />
            {pickerFloorsList}
          </Picker>

          <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30}}> Suite </Text>
          <Picker
            mode = 'dropdown'
            style = {{width: '80%', marginLeft: 30}}
            selectedValue={this.state.pickerSuiteID}
            onValueChange={(itemValue, itemIndex) => {
              this.setState({pickerSuiteID:itemValue})
            }}
          >
            <Picker.Item label = {defaultPlaceholder} value = {defaultPlaceholder} />
            {pickerSuitesList}
          </Picker>

          <TouchableHighlight 
            style={{ backgroundColor: 'gray', left: 200, width: 165, height: 40, justifyContent: 'center', alignItems: 'center', top: 10}}  
            activeOpacity = {1}
            onPress={() => {
              //Go to render map component 
            }}  
          >    
            <Text style = {{color: '#FFFFFF', fontSize: 18}}> Display suite map </Text>
          </TouchableHighlight>        
        </View>
      )

Solution

  • I suggest that you take your picker items generators outside the render method so your code should look something similar to this:

    (I tried this portion of code and it seems working just fine but I am not implementing your logic)

    import React from 'react';
    import {
      View,
      Text,
      Picker,
      TouchableHighlight,
      ScrollView,
    } from 'react-native';
    
    const defaultPlaceholder = 'default placeholder';
    
    const uniqueFacilityIDs = [
      {
        key: 'facility_key_1',
        facilityName: 'Facility Name 1',
        facilityID: 'facility_id_1'
      },
      {
        key: 'facility_key_2',
        facilityName: 'Facility Name 2',
        facilityID: 'facility_id_2'
      },
    ];
    
    const uniqueFloorIDs = [
      {
        key: 'floor_key_1',
        floorName: 'Floor Name 1',
        floorID: 'floor_id_1'
      },
      {
        key: 'floor_key_2',
        floorName: 'Floor Name 2',
        floorID: 'floor_id_2'
      },
    ];
    
    const uniqueSuiteIDs = [
      {
        key: 'suits_key_1',
        suiteName: 'Suits Name 1',
        suiteID: 'suits_id_1'
      },
      {
        key: 'suits_key_2',
        suiteName: 'Suits Name 2',
        suiteID: 'suits_id_2'
      },
    ];
    
    class App extends React.Component {
      state = {
        pickerFacilityID: defaultPlaceholder,
        pickerFloorID: defaultPlaceholder,
        pickerSuiteID: defaultPlaceholder,
      };
    
      renderFacilitiesPickerItems = () => uniqueFacilityIDs.map(a => {
        return <Picker.Item key={a.key} label={a.facilityName} value={a.facilityID}/>;
      });
    
      renderFloorsPickerItems = () => uniqueFloorIDs.map(a => {
        return <Picker.Item key={a.key} label={a.floorName} value={a.floorID}/>;
      });
    
      renderSuitesPickerItems = () => uniqueSuiteIDs.map(a => {
        return <Picker.Item key={a.key} label={a.suiteName} value={a.suiteID}/>;
      });
    
      render() {
        if (this.state.pickerFacilityID === defaultPlaceholder || this.state.pickerFloorID === defaultPlaceholder || this.state.pickerSuiteID === defaultPlaceholder) {
          return (
              <ScrollView>
                <View>
                  <Text style={{
                    fontSize: 18,
                    fontWeight: 'bold',
                    textAlign: 'center',
                    marginBottom: 25,
                    marginTop: 15
                  }}> Display Assets by Suites </Text>
    
                  <Text style={{
                    fontSize: 16.5,
                    textAlign: 'left',
                    marginLeft: 30,
                    marginBottom: 15,
                    textDecorationLine: 'underline'
                  }}>Filter suites by:</Text>
    
                  <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30}}> Facility </Text>
                  <Picker
                      mode='dropdown'
                      style={{width: '80%', marginLeft: 30}}
                      selectedValue={this.state.pickerFacilityID}
                      onValueChange={(itemValue, _itemIndex) => {
                        this.setState({pickerFacilityID: itemValue})
                      }}
                  >
                    <Picker.Item label={defaultPlaceholder} value={defaultPlaceholder}/>
                    {this.renderFacilitiesPickerItems()}
                  </Picker>
    
                  <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30}}> Floor </Text>
                  <Picker
                      mode='dropdown'
                      style={{width: '80%', marginLeft: 30}}
                      selectedValue={this.state.pickerFloorID}
                      onValueChange={(itemValue, itemIndex) => {
                        this.setState({pickerFloorID: itemValue})
                      }}
                  >
                    <Picker.Item label={defaultPlaceholder} value={defaultPlaceholder}/>
                    {this.renderFloorsPickerItems()}
                  </Picker>
    
                  <Text style={{fontSize: 16.5, textAlign: 'left', marginLeft: 30}}> Suite </Text>
                  <Picker
                      mode='dropdown'
                      style={{width: '80%', marginLeft: 30}}
                      selectedValue={this.state.pickerSuiteID}
                      onValueChange={(itemValue, itemIndex) => {
                        this.setState({pickerSuiteID: itemValue})
                      }}
                  >
                    <Picker.Item label={defaultPlaceholder} value={defaultPlaceholder}/>
                    {this.renderSuitesPickerItems()}
                  </Picker>
                </View>
              </ScrollView>
          )
        } else {
          return (
              <TouchableHighlight
                  style={{
                    backgroundColor: 'gray',
                    left: 200,
                    width: 165,
                    height: 40,
                    justifyContent: 'center',
                    alignItems: 'center',
                    top: 10
                  }}
                  activeOpacity={1}
                  onPress={() => {
                    //Go to render map component
                  }}
              >
                <Text style={{color: '#FFFFFF', fontSize: 18}}> Display suite map </Text>
              </TouchableHighlight>
          )
        }
      }
    }
    
    export default App;