martincito
martincito

Reputation: 81

Asynchronous Firebase data get on React Native

I'm building a React Native app using Firebase. I use the following method (among others) in one of my components to get data from Firebase:

loadMessagesFromFirebase(chatId){
    let messageList = [];
    let data = {};
    let message = {};
    const dataRef = firebase.database().ref('chats').child(chatId);
    dataRef.on('value', datasnap=>{
        data = datasnap.val()
    })
    for (var sayer in data){
        for (var m in data[sayer]){
            message = {sayer: sayer, text: m.text, time: new Date(m.created)};
            messageList.push(message);
        }
    }
    messageList.sort((a,b) => (a.time > b.time) ? 1: -1);
    this.setState({messageList: messageList});
  }

The problem is that occasionally, data will load as an empty dictionary (Object {}) and therefore, the message list will be empty. I assume this happens because I'm not giving Firebase enough time to load. How do I make this function asynchronous, so that I can add a "loading" state to the component and not display message information until it's finished loading?

async componentDidMount(){
    firebase.initializeApp(FirebaseConfig);
    this.loadMessagesFromFirebase(this.state.chatId);
    //once the messages are done loading
    this.setState({{loading: false}})
}

Additionally, is there a way to make sure that, if data is returned as an empty dictionary, it's because Firebase finished loading and NOT because there isn't data for this chat id?

Upvotes: 0

Views: 775

Answers (2)

vvs
vvs

Reputation: 1086

Answering this question even though OP seems to figured out the answer, since he hasn't explained the underlying concepts.

Firebase sdk uses async programming and observer pattern to provide real time updates.

The right way to Asynchronous Firebase data get on React Native world be as follows.

  1. Initialize firebase sdk only once during application startup. In React terms this can be done inside the constructor of the top level App component. See firebase docs for the steps. https://firebase.google.com/docs/database/web/start

  2. Inside component constructor or componentDidMount set up the call to firebase function to load data componentDidMount(){ this.loadMessagesFromFirebase(this.state.chatId); }

  3. In the load messages function at up async call to the firebase realtor database. More reading here https://firebase.google.com/docs/database/web/read-and-write

The main thing to remember here is that all code that has to run after data is available has to be triggered from writing the async call back. Modifying the example code from the question

loadMessagesFromFirebase(chatId){
    let data = {};
    let output = {};
    const dataRef = firebase.database().ref('chats').child(chatId);
    dataRef.once('value', datasnap=>{
        data = datasnap.val();

        // Do something really smart with the data and assign it to output
        ...... 
        output = data;
        // set updates to the state
        this.setState({output: output});
    })
  }

Note the use of once instead of the on function. The reason for this is that for getting subscription to the observer on can lead to the callback being triggered every time there is a change in data. This can lead to undesirable consequences if the component was designed to only get data once.

Further reading https://firebase.google.com/docs/database/web/read-and-write

In case it is desirable to have the component updated every time there is a data change then use the on function to set up a subscription to that data. However in such a case it is important to cancel the subscription inside of componentWillUnmount

https://firebase.google.com/docs/reference/js/firebase.database.Reference#off

This can be summarized as follows

`
// inside componentDidMount 
this.onValueChange = ref.on('value', function(dataSnapshot) { ... });


// Sometime later.... Inside componentWillUnmount
ref.off('value', this.onValueChange);`

Upvotes: 1

martincito
martincito

Reputation: 81

Figured it out thanks to vvs' comment. Everything that has to be done asynchronously has to go into that dataref.on() function.

loadMessagesFromFirebase(chatId){
    let messageList = [];
    let data = {};
    let message = {};
    const dataRef = firebase.database().ref('chats').child(chatId);
    dataRef.on('value', datasnap=>{
        data = datasnap.val()
        for (var sayer in data){
            for (var m in data[sayer]){
                message = {sayer: sayer, text: m.text, time: new Date(m.created)};
                messageList.push(message);
            }
        }
        messageList.sort((a,b) => (a.time > b.time) ? 1: -1);
        this.setState({messageList: messageList});
        this.setState({{loading: false}})
    })
  }

And this is how the function is called:

componentDidMount(){
    this.loadMessagesFromFirebase(this.state.chatId);
}

(Having firebase.initializeapp(FirebaseConfig) in there actually causes problems- it has to run once, and earlier, such as when setting up the app)

Upvotes: 0

Related Questions