Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading data into a React Native FlatList

I fully admit that I'm pretty new to Reactive Native at this point so I anticipate this being an easy answer and a potentially stupid question :) In fact, this may not be a RN issue per se, as I'll explain below.

What I have is the following code:

class MyScreen extends React.Component {

  constructor(inProps) {
    super(inProps);
    this.state = { listData : [ ] };
  }

  render() { return (
    <View>
      <FlatList data={ this.state.listData }
        renderItem={ ({item}) =>
          <Text>{item.firstName} {item.lastName}</Text>
        }
      />
    </View>
  );  }

  async componentDidMount() {
    const data = [ ];
    let keys = await AsyncStorage.getAllKeys();
    keys.forEach(async function(inKey) {
      let obj = await AsyncStorage.getItem(inKey);
      obj = JSON.parse(obj);
      data.push(obj);
    });
    this.setState({ listData : data });
  };

}

The goal is simply to load the data from AsyncStorage and dump it in the list. Fairly basic it would seem, right? Unfortunately, I can't get it to work: the list is empty.

Now, to cover the stupid things: yes, there is indeed data stored and yes, it's in the correct form (I know this because if I simply grab ONE SPECIFIC item and push() it into data then it appears in the list as expected).

In fact, I know what the problem is: it's that the setState() call is executing before the keys.forEach() call completes. I know this because if I replace the setState() call with this:

setTimeout(function() {
  this.setState({ listData : data });
}.bind(this), 1000);

...then suddenly my list is populated as expected (I can also throw some console.log()'s in there and see that the one after the keys.forEach() call actually executes BEFORE those inside the keys.forEach()). I also know that obviously the getItem() call is waiting before the JSON.parse() is called, otherwise this wouldn't work at all.

In other words: I know this is caused by the asynchronous nature of this code. What I can't seem to figure out is how to write it properly to avoid this.

I'm looking at it and I'm thinking "well, keys.forEach() is a blocking call, so how could it possibly not complete before the setState() call, ESPECIALLY given the async usage on the keys.forEach() callback AND the await usage on the getItem() call inside?"... the only thought I had was to drop an async in front of the keys.forEach() call, but that doesn't make any difference.

So, it seems to be a basic JavaScript question in essence, but I can't seem to solve it... but on the other hand, I'm not sure if I'm trying to do this in a way that I fundamentally shouldn't be in RN land and maybe THAT'S the real problem.

Note that the data has to be pulled dynamically from AsyncStorage because of the flow through the application (adds are done on another screen), so it seems to me that trying to load it in componentDidMount() makes sense, I'm just getting hung up with the asynchronous nature of the calls involved.

Any suggestions?

Thanks!

EDIT: I should also mention that I know one way I can solve this is to just remove the asynchronous nature entirely: rather than storing individual data items, instead store everything in a single object in AsyncStorage. Then I can read the ONE item, which would be an array (or would contain an array), then the only asynchronous part is the getItem() call, but I can use the callback version of that and make it work that way... and I'm not against that approach at all, but at this point I really want to understand what I'm doing wrong if for no other reason than to learn something because this seems like it SHOULD work :)

like image 782
Frank W. Zammetti Avatar asked Dec 17 '17 23:12

Frank W. Zammetti


1 Answers

keys.forEach execute the code in its own scope (in its own callback function), and that breaks your async / await pair.

Do this instead:

for (let inKey of keys) {
    let obj = await AsyncStorage.getItem(inKey);
    obj = JSON.parse(obj);
    data.push(obj);   
}

If you really want forEach, you have to write your own version that returns Promise, so you can hook await on it for example:

await keys.awaitableForEach(async function(inKey) {
like image 127
Val Avatar answered Oct 10 '22 23:10

Val