Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Picker onValueChange() called twice

I want to support localization using react-18next. This component shows a Picker, sets a LocalStorage key (the selected language) and change the app language. I noticed the onValueChange method is called twice. The first call (using a proper selection tap action on a Picker item) have the correct parameter (the language I have chosen), the second call have the value of the first Picker item, whenever the values is (!).

Example: if I select the English Picker item, I see the Picker switch to English (first _changeLanguage() call), and then again to "Device" (second _changeLanguage() call). I'm sure it's an async ops problem, not sure where..

@translate(['settings', 'common'], { wait: true })
export default class Settings extends Component {
    state = {};
     constructor(props)  {
        super(props);
    }
    componentWillMount() {
        this.getLang();
    }

    async _changeLanguage(ln) {
        const { t, i18n, navigation } = this.props;
        console.warn("_changeLanguage: ",ln)
        await this.promisedSetState({lang:ln})
        if(ln=="device") {
                console.warn("removing lang setting")
                await AsyncStorage.removeItem('@App:lang');
        } else {
                console.warn("lang setting: ", ln)
                await AsyncStorage.setItem('@App:lang', ln);
                i18n.changeLanguage(ln) 
        }

    };
    //get Language from AsyncStorage, if it has been previously set
    async getLang() {
        const value = await AsyncStorage.getItem('@App:lang');
        console.warn("getLangfrom asyncstorage:", value)
        if(value) await this.promisedSetState ({lang:value})
    }

    promisedSetState = (newState) => {
        return new Promise((resolve) => {
            this.setState(newState, () => {
                resolve()
            });
        });
    };
    render() {
        const { t, i18n, navigation } = this.props;
        const { navigate } = navigation;

        return (<View>
                    <Picker
                        selectedValue={this.state.lang}
                        onValueChange={(itemValue, itemIndex) =>this._changeLanguage(itemValue) }>
                        <Picker.Item color="#666" label="detected from device" value="device" />
                        <Picker.Item label="English" value="en" />
                        <Picker.Item label="Deutsch" value="it" />

                    </Picker>
                </View>);
    }
}

The code is based on the react-i18next Expo example https://github.com/i18next/react-i18next/tree/master/example/v9.x.x/reactnative-expo

like image 286
alfredopacino Avatar asked Mar 16 '19 18:03

alfredopacino


1 Answers

I'm not sure if this belongs here but if anyone is encountering the problem of onValueChange firing twice and is using react-native-picker-select instead of the default Picker then this solution may work for you.

I ended up using the itemKey property instead of value and setting the key of each item to be the same as its value. Note the example item would end up shaped like so: { key: 'value1', value: 'value1', label: 'Your Label' } where the key and value are identical.

Original Code:

<RNPickerSelect
   style={styles.picker}
   value={value}
   onValueChange={this.onValueChange}
   placeholder={placeholder}
   items={options}
/>

Fixed Code:

<RNPickerSelect
   style={styles.picker}
   itemKey={value}
   onValueChange={this.onValueChange}
   placeholder={placeholder}
   items={options}
/>

I got my inspiration from this: GitHub issue.

like image 157
Jesse Hill Avatar answered Sep 25 '22 10:09

Jesse Hill