3

I have been working on a chat app using Gifted-Chat and a Firebase RealTime database (and running it with Expo). At this point, the basic messaging works, but I am trying to enable to app to load earlier messages when the user scrolls up and hits the button that appears (I am aware of the GiftedChat prop for this). Unfortunately, I have been having trouble doing this and am a bit stumped.

There are two separate problems I have been running up against that I am aware of.

  1. Clicking the loadEarlier button gives me an undefined is not a function (near '...this.setState...' runtime error (clearly, something is wrong with the skeleton function I put there).
  2. The bigger issues is that I am still not clear on how to download the n number of messages before the oldest messages currently loaded. I have looked at the GiftedChat example and this post for help, but must confess that I am still lost (the best I can figure is that I need to sort the messages, possibly by timestamp, somehow get the right range, then parse them and prepend them to the messages array in state, but I cannot figure out how to do this, especially the later parts).

The relevant parts of the code for my chat screen are below, as is a screenshot of the structure of my firebase database. I would appreciate any help regarding both of these issues.

// Your run of the mill React-Native imports.
import React, { Component } from 'react';
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import * as firebase from 'firebase';
// Our custom components.
import { Input } from '../components/Input';
import { Button } from '../components/Button';
import { BotButton } from '../components/BotButton';
// Array of potential bot responses. Might be a fancy schmancy Markov
// chain like thing in the future.
import {botResponses} from '../Constants.js';
// Gifted-chat import. The library takes care of fun stuff like
// rendering message bubbles and having a message composer.
import { GiftedChat } from 'react-native-gifted-chat';
// To keep keyboard from covering up text input.
import { KeyboardAvoidingView } from 'react-native';
// Because keyboard avoiding behavior is platform specific.
import {Platform} from 'react-native';

console.disableYellowBox = true;

class Chat extends Component {
    state = {
        messages: [],
        isLoadingEarlier: false,
    };

    // Reference to where in Firebase DB messages will be stored.
    get ref() {
        return firebase.database().ref('messages');
    }

    onLoadEarlier() {
        this.setState((previousState) => {
          return {
            isLoadingEarlier: true,
          };
        });
        console.log(this.state.isLoadingEarlier)
        this.setState((previousState) => {
            return {
                isLoadingEarlier: false,
            };
        });
    }

    // Get last 20 messages, any incoming messages, and send them to parse.
    on = callback =>
        this.ref
          .limitToLast(20)
          .on('child_added', snapshot => callback(this.parse(snapshot)));
    parse = snapshot => {
        // Return whatever is associated with snapshot.
        const { timestamp: numberStamp, text, user } = snapshot.val();
        const { key: _id } = snapshot;
        // Convert timestamp to JS date object.
        const timestamp = new Date(numberStamp);
        // Create object for Gifted Chat. id is unique.
        const message = {
            _id,
            timestamp,
            text,
            user,
        };
        return message;
    };
    // To unsubscribe from database
    off() {
        this.ref.off();
    }

    // Helper function to get user UID.
    get uid() {
        return (firebase.auth().currentUser || {}).uid;
    }
    // Get timestamp for saving messages.
    get timestamp() {
        return firebase.database.ServerValue.TIMESTAMP;
    }

    // Helper function that takes array of messages and prepares all of
    // them to be sent.
    send = messages => {
        for (let i = 0; i < messages.length; i++) {
            const { text, user } = messages[i];
            const message = {
                text,
                user,
                timestamp: this.timestamp,
            };
            this.append(message);
        }
    };

    // Save message objects. Actually sends them to server.
    append = message => this.ref.push(message);

    // When we open the chat, start looking for messages.
    componentDidMount() {
        this.on(message =>
          this.setState(previousState => ({
              messages: GiftedChat.append(previousState.messages, message),
          }))
        );
    }
    get user() {
        // Return name and UID for GiftedChat to parse
        return {
            name: this.props.navigation.state.params.name,
            _id: this.uid,
        };
    }
    // Unsubscribe when we close the chat screen.
    componentWillUnmount() {
        this.off();
    }

    render() {
        return (
        <View>
            <GiftedChat
                loadEarlier={true}
                onLoadEarlier={this.onLoadEarlier}
                isLoadingEarlier={this.state.isLoadingEarlier}
                messages={this.state.messages}
                onSend={this.send}
                user={this.user}
            />
         </View>
        );
    }

}
export default Chat;

Screenshot of DB

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Fake Name
  • 813
  • 2
  • 9
  • 17

3 Answers3

1

For your first issue, you should declare your onLoadEarlier with => function so as to get the current instance this i.e. your code should look like below:

onLoadEarlier = () => {
    this.setState((previousState) => {
      return {
        isLoadingEarlier: true,
      };
    }, () => {
       console.log(this.state.isLoadingEarlier)
       this.setState((previousState) => {
          return {
             isLoadingEarlier: false,
          };
       });
    }); 
}

Also, setState is asynchronous in nature, so you should rather depend on the second parameter of the setState i.e. the callback to ensure that the next lines of code execute synchronously.

Lastly, if you are using class syntax then you should declare the state in constructor like below:

class Chat extends Component {
   constructor (props) {
      super (props);
      state = {
         messages: [],
         isLoadingEarlier: false,
      };
   }
  ......

    onLoadEarlier = () => {
      this.setState((previousState) => {
        return {
          isLoadingEarlier: true,
        };
      }, () => {
         console.log(this.state.isLoadingEarlier)
         this.setState((previousState) => {
            return {
               isLoadingEarlier: false,
            };
         });
      }); 
  }
  ...
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Suraj Malviya
  • 3,685
  • 1
  • 15
  • 22
  • Thank you, this all works. I am still pretty new to React-native (first project), so am wondering why I should declare state in the constructor? – Fake Name Dec 28 '18 at 21:32
  • [This](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) might help you understand how the class members are defined and declared. – Suraj Malviya Dec 29 '18 at 06:54
  • Thank you, I did not realize how complex javascript is. After going through the page though, I am still confused as to why a constructor needs to be used to declare state, given that the code works for me after applying your first fixes without adding a constructor. – Fake Name Dec 29 '18 at 23:47
  • Sorry, never mind, I think that I finally mostly understand the whole binding thing. If you get a chance to look into how to load older messages though, I would really appreciate it. – Fake Name Dec 31 '18 at 00:08
0

For loading the last messages from firebase , I recommend using limitToLast function on your reference. You should afterwards order the results by date before calling append in gifted chat.

Omri Haim
  • 121
  • 1
  • 3
  • The trouble I was running into with that was figuring out how to avoid redownloading all of the messages that I already have. Is there a way to do this? – Fake Name Mar 11 '19 at 14:53
0

For the second question, it should be the same with this question How Firebase on and once differ?

You can using filter feature in Firebase for example using createdAt field to compare with last loaded message to load more.

Lap Tran
  • 89
  • 5