Trouble updating Picker component from Async Storage in React Native

411 Views Asked by At

I'm using a Picker component to let the user set a value for how frequently they want to be reminded about something. In the code below, I'm saving the result in the component's state as well as saving it to the device with Async Storage:

const [frequency, setFrequency] = useState('year');

...

<Picker
    selectedValue={frequency}
    style={styles.picker}
    itemStyle={styles.pickerItem}
    onValueChange={(itemValue, itemIndex) => { 
        (async () => { 
            await AsyncStorage.setItem(`@circle_${circle.name}_frequency`, itemValue)
        })();
        setFrequency(itemValue);
    }}
    mode={'dropdown'}
    prompt={'Reminder every:'}
>
    <Picker.Item label="Never" value="never" />
    <Picker.Item label="Day" value="day" />
    <Picker.Item label="Week" value="week" />
    <Picker.Item label="Year" value="year" />
    etc...
</Picker>

I'd also like to have the component grab the saved data and set that as the state when first rendering.

    useEffect(() => {
        const fetchFrequency = async () => {
            let storedFrequency =  await AsyncStorage.getItem(`@circle_${circle.name}_frequency`);
            if (storedFrequency != null) { 
                setFrequency(storedFrequency);
            };
        }
        fetchFrequency();
    }, []);

Based on the limited amount I know about Async Storage, this seems to make sense. I think it's

  • awaiting the result of grabbing the value from storage
  • setting the state
  • rendering the component (this could be happening before setting state as well, but I figure it would render again when the state changes)
  • updating both storage and state when the user chooses a new option

However, this doesn't work. If I navigate away and then back to the page, the state has been reset.

UPDATE:

If I console.log the itemValue in the onValueChange async function this is what I get:

onValueChange={(itemValue, itemIndex) => { 
                        (async () => { 
                            await AsyncStorage.setItem(`@circle_${circle.name}_frequency`, itemValue)
                            console.log(itemValue)
                        })();
                        setFrequency(itemValue);
                    }}

When changing the value to 'never', it prints

never 
never

When I navigate away and then come back, without even touching the compnent it prints out:

week
week
never
never
year
year

or

year
never
year
year

or some other long string of values which shows that there's a feedback loop going on somewhere.

2

There are 2 best solutions below

0
On BEST ANSWER

It looks like the problem was an issue with the Picker itself and how it calls onValueChange every render rather than only when changed. I found a temporary solution in this thread for until it gets fixed: https://github.com/lawnstarter/react-native-picker-select/issues/112#issuecomment-634038287

3
On

Your expression AsyncStorage.setItem() is not firing because you forget to invoke Self-Invoking Functions inside the callback function of onValueChange.

    onValueChange={(itemValue, itemIndex) => { 
        (async () => { 
            await AsyncStorage.setItem(`@circle_${circle.name}_frequency`, itemValue)
        })(); // I will invoke myself
        setFrequency(itemValue);
    }}

UPDATED (following the updated question):

I didn't spot any more bugs on your given snippet code and I don't know what's going on with your full source code. Anyway, I have created a super simple working snippet code following by the code in your question, so you can just copy into your project.

import React, {useState, useEffect} from 'react';
import {Picker, AsyncStorage} from 'react-native';

export default function App() {
  const [frequency, setFrequency] = useState('year');
  useEffect(() => {
    const fetchFrequency = async () => {
      let storedFrequency = await AsyncStorage.getItem('@circle_circle_name_frequency');
      if (storedFrequency != null) {
        setFrequency(storedFrequency);
      }
    };
    fetchFrequency();
  }, []);

  return (
    <Picker
      selectedValue={frequency}
      onValueChange={(itemValue, itemIndex) => {
        (async () => {
          await AsyncStorage.setItem('@circle_circle_name_frequency', itemValue);
        })();
        setFrequency(itemValue);
      }}
      mode={'dropdown'}
      prompt={'Reminder every:'}>
      <Picker.Item label="Never" value="never" />
      <Picker.Item label="Day" value="day" />
      <Picker.Item label="Week" value="week" />
      <Picker.Item label="Year" value="year" />
    </Picker>
  );
}

Hope this can help!

PS: I see you put expo tag on your question and I just wanna remind that, if you preview the project on the web browser, your storedFrequency inside useEffect will always be null because the browser doesn't support AsyncStorage.