n4kz / react-native-material-dropdown

Material dropdown with consistent behaviour on iOS and Android
Other
732 stars 597 forks source link

How can I reset the value and text of dropdown? #27

Open tanmaybhatt opened 6 years ago

tanmaybhatt commented 6 years ago

I am using two dropdowns on the single screen and I have to reset the second dropdown when the value of first dropdown is changed. I tried to use the value prop and bound it to a state variable but the text of the dropdown does not change when state changes. Am I missing out something here?

abartolo commented 6 years ago

I am also having this issue. However my use case is different. For example I have an extra option called 'Create a new team' which when selected performs another task but DOES NOT change the value property for the drop down.

It seems like selecting an options from the drop down is overriding value passed through value property. Please find my Dropdown component below.

            <Dropdown
                            label='Current Team'
                            data={teams}
                            value={selectedTeam.name || ''}
                            baseColor={'rgba(255,255,255, .38)'}
                            textColor={'rgba(255,255,255, .87)'}
                            itemColor={'rgba(0,0,0, .54)'}
                            selectedItemColor={'rgba(0,0,0, .87)'}
                            labelFontSize={14}
                            labelHeight={14}
                            onChangeText={(value, index, data) => this.handleCurrentTeamChange(data[index].team)}
                            containerStyle={MainDrawerStyles.selectTeamContainer}
                        />

1) Select 'Create a new team' options image

The 'this.handleCurrentTeamChange()' is not changing the selectedTeam object but the dropdown is setting 'Create a new team' as the value in the UI.

2) Should not change since value property did not change. But it does :( image

abartolo commented 6 years ago

@tanmaybhatt Hey any luck on a possible workaround?

tanmaybhatt commented 6 years ago

Yeah, as a workaround for my case I made some changes to react-native-material-dropdown/src/components/dropdown/index.js

I made these changes to componentWillReceiveProps function which did the thing. Line 118

componentWillReceiveProps({ value }) {
    if (value !== this.props.value) {
      this.setState({ value });
    }
  }

changed to

componentWillReceiveProps({ value }) {
      this.setState({ value });
  }

I do not know why this worked, maybe something was already changing the props value. I guess I did not get the time to check the rest of the code.

This worked in my case. However, your case is different. Perhaps you should edit this function according to your needs. So, when the value is equal to "CREATE_NEW_TEAM" or whatever the value of your Create new team item, the label of the dropdown should not change.

componentWillReceiveProps({ value }) {
    if (value !== "CREATE_NEW_TEAM") {
      this.setState({ value });
    }
  }

You may also assign this value to some prop and then check here using that prop.

Hope this helps!

abartolo commented 6 years ago

@tanmaybhatt Thanks for the detailed explanation. I was hoping we didn't need to modify the library. However I did find that this component is storing its value and using the value passed in props. Luckily this isn't a major bug in our app that we are able to push this back a bit.

@n4kz Hey Alexander thanks for the libraries! Any feedback regarding our issue/needs and if this will be looked into for a possible fix?

dracostheblack commented 6 years ago

@tanmaybhatt's changed to worked for me as well.

n4kz commented 6 years ago

Thanks for issue and sorry for delayed reply. Described rendering problems are related to PureComponent usage, as Dropdown itself and TextField are based on them. I will implement something like extraData prop on FlatList component, to force re-render of TextField (or any other component used as Dropdown base) in next major releases of TextField and Dropdown.

michaelaflores commented 6 years ago

Considering this is still an issue, @n4kz would you accept a PR with @tanmaybhatt's workaround as a temporary patch?

velevtzvetlin commented 6 years ago

call forceUpdate via the ref exposed by this component

DevJulianSalas commented 6 years ago

@tvelev92 i tried with forceUpdate but doest not work well. I added a ref like

<Dropdown
                label="Todas las Listas"
                data={this.state.lists}
                baseColor="#86939e"
                itemTextStyle={{ fontWeight: "bold" }}
                fontSize={16}
                textColor="#86939e"
                labelFontSize={15}
                containerStyle={{
                  width: "90%",
                  marginLeft: 20,
                  marginRight: 20
                }}
                value={this.state.selectedList}
                onChangeText={this.selectHandlerList}
                rippleDuration={0}
                ref = {(c) => { this.form.listas = c; }}
              />

and then When I use a handlerEvent to hint to clear fields from a Button using onPress

handleClearFields = () => {
    this.forms.listas.forceUpdate();    // this does not work anymore.
    for (let key in this.form) {
      this.form[key].clear()
    }

  }

Could you provide to me an working example, please?

ashishlamse commented 5 years ago

I was facing the same problem and I try to find solution like below.Please have a look and no need to update plugin. {this.props.getAreasSuccessFailure && !this.props.isFetching ? <Dropdown label='Select Your Area' data={this.state.selectedCityAreas} /> : null}

jsite-pl commented 4 years ago

I had the same problem. My solution is add ref and setState on it. Example of code for single dropdown:

 constructor(props) {
  super(props);
  this.myDropdown = null;
}

resetMyDropdown () {
  if (this.myDropdown) {
    this.myDropdown.setState({ value: '' });
  }
}

render() {
  return (
    <Dropdown
      label='Label'
      data={data}
      value={this.getSelectedName(data)}
      ref={c => (this.myDropdown = c)}
    />
  );
}

Example for nested components from my app:

constructor(props) {
  super(props);
  numberOfComponents = 3;
  this.chooseDropdowns = Array(numberOfComponents).fill(0);
}

renderChoose() {
  return Array(numberOfComponents)
    .fill(0)
    .map((_, index) => getData(index)) // GetData return array with objects {value: 'name of select', id: 'SOMEID'}, eg. [{value: 'first option', id: 1}...]
    .map((data, index) => (
      <View key={index}>
        <Dropdown
          label={getLevelLabelByIndex(index)} //getLevelLabelByIndex return string for each dropdown
          data={data}
          onChangeText={value => {
            // here all deeper dropdowns will be cleared
            for (let i = index + 1; i < numberOfComponents; i++) {
              if (this.chooseDropdowns[i]) {
                this.chooseDropdowns[i].setState({ value: '' });
              }
            }
            return this.selected(data, value, index);
          }}
          value={this.getSelectedName(data, index)}
          ref={c => (this.chooseDropdowns[index] = c)}
        />
      </View>
    ));
}
akhilsanker commented 4 years ago

Hi, @tanmaybhatt Facing the same issue. Any update. "react-native": "0.59.8", "react-native-modal-dropdown": "^0.7.0",

Any suggestion is appreciable. Thanks

jsite-pl commented 4 years ago

@akhilsanker

I made clean rn project with that function for You. :) If you have any problems here, please send your code pls :)

App.js

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import { Dropdown } from 'react-native-material-dropdown';

/* Data for first dropdown */
const firstLevel = [
  { value: 'First option without suboptions', id: 1 },
  { value: 'Second option with single suboption', id: 2 },
  { value: 'Third option with multiple suboptions', id: 3 }
];
/* Data for second dropdown */
const secondLevel = [
  { value: 'First suboption for firstLevel with ID 2', id: 'A', parentId: 2 },
  { value: 'First suboption for firstLevel with ID 3', id: 'B', parentId: 3 },
  { value: 'Second suboption for firstLevel with ID 3', id: 'C', parentId: 3 },
];

type Props = {};
export default class App extends Component<Props> {
  state = {
    idOfFirstDropdownValue: null, // value of first dropdown (id prop from data object)
    idOfSecondDropdownValue: null // value of second dropdown (id prop from data object)
  };
  constructor(props) {
    super(props);
    this.firstDropdown = null; // variable for referring to first dropdown
    this.secondDropdown = null; // variable for referring to second dropdown
  }
  resetDropdown(dropdown) {
   /* dropdown variable for select first dropdown or second dropdown to reset */
    switch (dropdown) {
      case 'second':
        if (this.secondDropdown) {
          this.secondDropdown.setState({ value: '' });
          this.setState({ idOfSecondDropdownValue: null });
        }
        break;
      default:
        if (this.firstDropdown) {
          /* line below reset dropdown */
          this.firstDropdown.setState({ value: '' });
          /* line below reset just my state, i reset that, because i filter list by that variable */
          this.setState({ idOfFirstDropdownValue: null });
        }
    }
  }
  render() {
    /* make additional array with filtered second Level */
    const secondLevelData = secondLevel.filter(el => el.parentId === this.state.idOfFirstDropdownValue);
    return (
      <View style={styles.container}>
        <Dropdown
          label='First level dropdown'
          data={firstLevel}
          onChangeText={(value, index, data) => {
            const id = data[index].id;
            this.setState({idOfFirstDropdownValue: id});
            /* Here is call function for reset second dropdown */
            this.resetDropdown('second');
          }}
          containerStyle={{width: '100%'}}
          ref={c => (this.firstDropdown = c)}
        />
        <Dropdown
          label='Second level dropdown'
          data={secondLevelData}
          onChangeText={(value, index, data) => {
            const id = data[index].id;
            this.setState({idOfSecondDropdownValue: id});
          }}
          containerStyle={{width: '100%'}}
          ref={c => (this.secondDropdown = c)}
        />
        <Text>Value of first dropdown: {this.state.idOfFirstDropdownValue}</Text>
        <Text>Value of second dropdown: {this.state.idOfSecondDropdownValue}</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

package.json

{
  "name": "Rn59SampleApp",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest"
  },
  "dependencies": {
    "react": "16.8.3",
    "react-native": "0.59.8",
    "react-native-material-dropdown": "^0.7.0"
  },
  "devDependencies": {
    "@babel/core": "^7.8.7",
    "@babel/runtime": "^7.8.7",
    "babel-jest": "^25.1.0",
    "jest": "^25.1.0",
    "metro-react-native-babel-preset": "^0.58.0",
    "react-test-renderer": "16.8.3"
  },
  "jest": {
    "preset": "react-native"
  }
}
AlbinBackstrom commented 4 years ago

I cleared it by using the value prop and state. Having 3 dropdowns and if the top one is used it should reset the other two, if the second one is changed it should only reset the third one. Using functional components and useState hook. Sorry about the somewhat messy code, will refactor it but should give you a grasp how to do it

const [pickedCategories, setPickedCategories] = useState('');
const selectedCategories = (name, value) => {
   if (name === 'mainCategory' && pickedCategories.subCategory) {
      setPickedCategories({
        mainCategory: '',
        subCategory: '',
        subSubCategory: '',
      });
    }

    if (
      (name === 'mainCategory' || name === 'subCategory') &&
      (pickedCategories.subCategory || pickedCategories.subSubCategory)
    ) {
      setPickedCategories((prevState) => ({
        ...prevState,
        [name]: '',
        subSubCategory: '',
      }));
    }

    setPickedCategories((prevState) => ({
      ...prevState,
      [name]: value,
    }));
  };
     <Dropdown
        label='Huvudkategori'
        value={pickedCategories.mainCategory}
        data={renderCategories('main')}
        onChangeText={(text) => selectedCategories('mainCategory', text)}
      />

      <Dropdown
        label='Kategori'
        value={pickedCategories.subCategory}
        data={renderCategories('sub')}
        onChangeText={(text) => selectedCategories('subCategory', text)}
      />

      <Dropdown
        label='Underkategori'
        value={pickedCategories.subSubCategory}
        data={renderCategories('subSub')}
        onChangeText={(text) => selectedCategories('subSubCategory', text)}
      />