Search code examples
react-nativemodal-dialogreact-reduxreact-modal

Index of selected value remains same for all dynamic spinners React-native


I am trying to make dynamic screen where i am adding multiple CustomSpinners using forEach loop in react-native.

The problem I am facing is that whenever I am selecting value from any spinner that value gets updated for all spinners, as the index remains the same. Suppose when I click on color spinner and choose Cyna which is at index 2 and when I type this.setState({name: index}); it calls Render method again and the whole view get draw again and as the itemName={this.state.name} is set to 2 so it will auto select all the spinner values based on index 2 , what I want that when I tap on Color spinner and choose cyan color then only that Color spinner gets updated instead of updating all spinners at same index.

Can any one let me know how to achieve this? I am already scratching my head from past 2 days on this. Please let me know your suggestions that will really help me lot.

Here is my code :-

onClickColor(data) {
  this.setState({ name: index });
}   

renderSpinerData(item) {
  return <CustomDynamicSpinner
           title={"Chosse  " + item.label}
           listArray={item.options}
           onClick={(data,index) => this.onClickDropdown(data,index)}
           itemName={this.state.name}
           btnStyle={{
             height: 42,
             marginBottom: 2,
             borderBottomWidth: 1 / 2,
             justifyContent: "center"
           }}
           txtColor={appColor.lightGrey}
           style={{
             height: 42,
             marginBottom: 2,
             borderBottomWidth: 1 / 2
           }}
           closeIconButtonStyle={styles.closeButtonStyle}
         />
       }; 

    renderConfigurableProductDetail() {
      let array=[];
      if (CustomConfigArray.length>0){
        array = CustomConfigArray;
      } else {
        array = this.props.ProductDetailState.productData.configurable;
      }
      {
        return array.map((item) => {
          if(item.label!="Size"){
            return (
              <View style={{ flex: 1, backgroundColor: "white", flexDirection: "column", marginTop: 8 }}>
                <CustomText style={{ fontSize: 16, fontFamily: "futuraLigtBt", marginLeft: 6, paddingLeft: 15, paddingRight: 15, paddingTop: 5, paddingBottom: 5 }}>
                  {item.label}
                </CustomText>
                {this.renderSpinerData(item)}
              </View>;
            )
          }
        })
      }
    };

My CustomSpiner class :-

  class DynamicListViewModal extends Component {
    constructor(props) {
    super(props);

    this.state = {
      dataSource: ds.cloneWithRows(this.props.listArray),
      listArray: this.props.listArray
    };
    this.handleClick = this.handleClick.bind(this);
    this.renderRow = this.renderRow.bind(this);
    this.renderList = this.renderList.bind(this);
  }
  handleClick(data, index) {
    this.props.onClick(data, index);
    this.props.onClose();
  }
  renderRow(rowData) {
    const separatorStyle = this.props.separatorStyle;
    const rowTextStyle = this.props.rowText;
    const rowStyle = this.props.rowStyle;
    const rowId = this.props.listArray.indexOf(rowData);
    let separator = <View style={separatorStyle} />;

    let row = (
      <View style={rowStyle}>
        <Text style={rowTextStyle}>{rowData.label}</Text>
      </View>
    );

    if (this.props.renderRow) {
      row = this.props.renderRow(rowData, rowId);
    }

    return (
      <View>
        <TouchableOpacity onPress={() => this.handleClick(rowData, rowId)}>
          {row}
        </TouchableOpacity>
        {separator}
      </View>
    );
  }
  renderList() {
    const listViewStyle = this.props.listViewStyle || DefaultStyles.listView;
    return (
      <ListView
        style={listViewStyle}
        dataSource={this.state.dataSource}
        renderRow={(rowData) => this.renderRow(rowData)}
        automaticallyAdjustContentInsets={false}
        enableEmptySections={true}
      />
    );
  }
  render() {
    const containerStyle = this.props.containerStyle;
    const topBarStyle = this.props.topBarStyle;
    const iconContainerStyle = this.props.iconContainerStyle;
    const closeIconButtonStyle = this.props.closeIconButtonStyle;
    const titleStyle = this.props.titleStyle;
    const title = this.props.title;

    return <View style={containerStyle}>
      <View style={topBarStyle}>
        <View style={iconContainerStyle} />
        <Text style={[titleStyle, { fontWeight: "bold", fontSize: 17 }]}>
          {title}
        </Text>
        <TouchableOpacity style={iconContainerStyle} onPress={() => this.props.onClose()}>
          <Image source={require("../../assets/cancel.png")} style={closeIconButtonStyle} />
        </TouchableOpacity>
      </View>
      {this.renderList()}
    </View>;
  }
}

class CustomDynamicSpinner extends Component {
  constructor(props) {
    super(props);

    this.state = {
      data: this.props.data,
      popoverIsOpen: false
    };

    this.onClick = this.onClick.bind(this);
  }
  onClick(data) {
    console.log("selected data:", data);
  }
  render() {
    const onClick = this.props.onClick || this.onClick;
    return <View>
      <TouchableOpacity style={this.props.btnStyle} onPress={() => this.setState(
        { popoverIsOpen: true }
      )}>
        <Text style={{ color: this.props.txtColor }}>
          {(this.props.itemName!="value" || this.props.itemName == 0) ? this.props.listArray[this.props.itemName].label : "Please select"}
        </Text>
      </TouchableOpacity>
      <Modal animationType={"slide"} transparent={false} visible={this.state.popoverIsOpen} onRequestClose={() => {
        console.log("Modal has been closed.");
      }}>
        <View>
          <DynamicListViewModal listArray={this.props.listArray} title={this.props.title} onClick={onClick} onClose={() => this.setState(
            { popoverIsOpen: false }
          )} containerStyle={this.props.containerStyle} listViewStyle={this.props.listViewStyle} separatorStyle={this.props.separatorStyle} topBarStyle={this.props.topBarStyle} titleStyle={this.props.titleStyle} iconContainerStyle={this.props.iconContainerStyle} closeIconButtonStyle={this.props.closeIconButtonStyle} rowTextStyle={this.props.rowTextStyle} rowStyle={this.props.rowStyle} />
        </View>
      </Modal>
    </View>;
  }
}

Response From Api :-

 "configurable": [{
              "id": "142",
              "code": "size",
              "label": "Size",
              "options": [{
                "attribute_id": "142",
                "atribute_code": "size",
                "id": "171",
                "label": "XL",
                "products": [
                  "2071",
                  "2074"
                ]
              }, {
                "attribute_id": "142",
                "atribute_code": "size",
                "id": "172",
                "label": "L",
                "products": [
                  "2072"
                ]
              }]
            },
            {
              "id": "93",
              "code": "color",
              "label": "Color",
              "options": [{
                "attribute_id": "93",
                "atribute_code": "color",
                "id": "50",
                "label": "Blue",
                "products": [
                  "2071"
                ]
              },
              {
                "attribute_id": "93",
                "atribute_code": "color",
                "id": "60",
                "label": "Black",
                "products": [
                  "2072"
                ]
              }, {
                "attribute_id": "93",
                "atribute_code": "color",
                "id": "64",
                "label": "Cyna",
                "products": [
                  "2072"
                ]
              }, {
                "attribute_id": "93",
                "atribute_code": "color",
                "id": "61",
                "label": "White",
                "products": [
                  "2071",
                  "2074"
                ]
              }
              ]
            },
            {
              "id": "148",
              "code": "format",
              "label": "Format",
              "options": [{
                "attribute_id": "148",
                "atribute_code": "format",
                "id": "102",
                "label": "Download",
                "products": [
                  "2072",
                  "2071",
                  "2074"
                ]
              },
              {
                "attribute_id": "148",
                "atribute_code": "format",
                "id": "103",
                "label": "File",
                "products": [
                  "2071",
                  "2074"
                ]
              }
              ]
            }
            ]

Your Help would be greatly appreciated !!!

Regards


Solution

  • When new state depends on previous state you should use the format setState((prevState, props) => {}) see reference in the docs. In any way you should never mutate state directly and this is what this line does selectValue[index] = value since selectValue is just a reference to this.state

    So here is what exactly you need to do :-

    // load selectedDropDownValue data from state with the item's id
    // pass the item value to the change function
    <Picker
      selectedValue={this.state.selectedDropDownValue[item.id]}
      onValueChange={(itemValue, itemIndex) => this.onClickDropdown(itemValue, itemIndex, item)}
    >
      {this.loadData(item)}
    </Picker>
    

    onClickDropdown(data, index, item){
      // save each items selected data with their id
      this.setState((prevState) => {
        const value = Object.assign({}, prevState.selectedDropDownValue, { [item.id]: data});
        return { selectedDropDownValue: value};
      });
    }