Search code examples
javascriptreactjsreact-nativeasyncstorage

React Native Issue - Initialize state by calling another method from constructor


I have small React Native Component in which I try to initialize state by reading values from local storage (AsyncStorage). But when I call the outside method from constructor I get undefined as state value.

Here is part of my code:

constructor(props) {
    super(props);
    this.state = {
       languageChecked: I18n.locale, 
       anonymityChecked: this.pickLocalKey('anonymity', true), 
       locationChecked: this.pickLocalKey('location', false),  
       notificationChecked: this.pickLocalKey('notification', false),
       statisticsChecked: this.pickLocalKey('statistics', false),
    };
    console.log("STATE: " + this.state.anonymityChecked); // UNDEFINED
}

pickLocalKey(key, defaultValue) {
  AsyncStorage.getItem(key).then((value) => {
    const item = (value !== null && value !== undefined) ? value : defaultValue;
    console.log(item);
    return item;
  });
}

Does anyone knows how to solve this issue? Thank you in advance


Solution

  • There are several issues with the code that you pasted in your question.

    Firstly the constructor is synchronous and you trying to perform asynchronous tasks inside it. This is not a good idea.

    If you need to load values from AsyncStorage and store them in state you should do it in the componentDidMount

    So set initial values in the constructor, adding an additional value loaded: false. This will become important later on.

    constructor (props) {
      super(props);
      this.state = {
        languageChecked: I18n.locale,
        anonymityChecked: true,
        locationChecked: false,
        notificationChecked: false,
        statisticsChecked: false,
        loaded: false // <- add this additional value
      };
    }
    

    Currently your pickLocalKey function isn't actually returning anything. The return value is trapped inside a promise. Luckily we can convert the function to use async/await. So it should look something like this:

    pickLocalKey = async (key, defaultValue) => {
      try {
        let value = await AsyncStorage.getItem(key);
        const item = (value !== null && value !== undefined) ? value : defaultValue;
        return item === 'true'; // this will make sure we get a Boolean back as long as you store strings 'true' and 'false' for your Booleans
      } catch (error) {
        return defaultValue;
      }
    }
    

    Note that await can throw so it is always best to wrap it in a try/catch.

    Then in your componentDidMount you should make the calls to retrieve the values from AsyncStorage

    async componentDidMount () {
      // these won't throw as we are catching the error before it gets here
      let anonymityChecked = await this.pickLocalKey('anonymity', true);
      let locationChecked = await this.pickLocalKey('location', false);
      let notificationChecked = await this.pickLocalKey('notification', false);
      let statisticsChecked = await this.pickLocalKey('statistics', false);
      // set all items in state in one go as multiple calls to setState are not good
      this.setState({
        anonymityChecked,
        locationChecked,
        notificationChecked,
        statisticsChecked,
        loaded: true . // <- we set loaded to true
      }, () => console.log(this.state)); // this is how you check state after it has been set
    }
    

    Then in your render you should now use the fact that loaded is now true to render the correct view:

    render () {
      if (this.state.loaded) {
        return (
          this is the view that you actually want to show
        )
      } else {
        return (
          this is the view that you will show when everything is loading
        )
      }
    }
    

    Here are some articles that are worth reading: