3

Environment : expo 36.0.0 / React Native 0.61 / react-native-gifted-chat 0.13.0 / firebase 7.6.0

My goal: Render only messages specific to a specific chat channel on the Gifted Chat UI.

Expected results: Only messages pertaining to a given channel should be displayed on the Gifted Chat UI.

Actual results :

  1. When I navigate from one chat channel to another the total messages cummulate.
  2. When I come back to an earlier channel, the messages for the same chat channel repeat more than once.
  3. Warning: Encountered two children with the same key, %s. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.%s

What have I tried so far? 1. Relaunch subscription to new chat channel every time the user navigates from one channel to another using componentDidUpdate. 2. Set State of messages array to an empty array every time user changes chat channel. 3. Unsubscribe from the previous node in Firebase and subscribe to a new node in componentDidUpdate. Here node represents the chat channel identified by an ID in Firebase. Each node contains children that are all the messages pertaining to that specific chat channel.

  async componentDidMount() {
    await this.getSessionInfo();
    await this.getCurrentProfile();
    await this.getMessages();
  };

  async componentDidUpdate(prevProps) {
    /* sessionID represent a chat channel and corresponding node in Firebase */
    /* If user navigates from one channel to another we establish a connection with new node and get the 
    corresponding messages */
    if (this.props.navigation.state.params.sessionID !== prevProps.navigation.state.params.sessionID) {
      await this.getSessionInfo();
      await this.getCurrentProfile();
      /* Subscribe to new chat channel */
      await this.ref();
      await this.getMessages();
    }
  };

  async componentWillUnmount() {
    /* Unsubscribe from database */
    await this.off();
  }

  /* Get messages to display after component is mounted on screen */
  getMessages = () => {
    this.connect(message => {
      this.setState(previousState => ({
        messages: GiftedChat.append(previousState.messages, message),
      }))
    });
  }

  /* Each sessionID corresponds to a chat channel and node in Firebase  */
  ref = () => {
    return database.ref(`/sessions/${this.state.sessionID}`);
  }

  /* Fetch last 20 messages pertaining to given chat channel and lister to parse new incoming message */
  connect = (callback) => {
    this.ref()
      .limitToLast(20)
      .on('child_added', snapshot => callback(this.parse(snapshot)));
  }

    /* newly created message object in GiftedChat format */
    parse = snapshot => {
    const { timestamp: numberStamp, text, user } = snapshot.val();
    const { key: _id } = snapshot;
    const timestamp = new Date(numberStamp);

    const message = {
      _id,
      timestamp,
      text,
      user,
    };
    return message;
  };

  /* function that accepts an array of messages then loop through messages */
  send = (messages) => {
    for (let i = 0; i < messages.length; i++) {
      const { text, user } = messages[i];
      const message = {
        text,
        user
      };
      this.append(message);
    }
  };

  /* append function will save the message object with a unique ID */
  append = message => this.ref().push(message);

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Sujay
  • 570
  • 3
  • 8
  • 24
  • 1
    Try setting the state to an emtpy array using chaining similar to Mike Kamermans' answer here https://stackoverflow.com/questions/29994296/how-to-use-setstate-in-react-to-blank-clear-the-value-of-an-array – Brett Gregson Jan 03 '20 at 15:46
  • 1
    Thank you very much Brett for taking the time to answer my question and Ivan and Frank for editing my question and making it more understandable. The conceptual explanation in Mike Kamerman's answer helped me to implement a version of option 1 that figures in his answer. – Sujay Jan 03 '20 at 17:26
  • glad you found the answer – Brett Gregson Jan 06 '20 at 08:24

1 Answers1

3

Following Brett Gregson's advice and Mike Kamerman's answer, I implemented the following solution.

  1. When the user navigates from one channel to another, I trigger a set of functions. this.ref() function subscribes to new channel (node in Firebase).
  async componentDidUpdate(prevProps) {
    if (this.props.navigation.state.params.sessionID !== prevProps.navigation.state.params.sessionID) {
      await this.getSessionInfo();
      await this.ref();
      await this.switchSession();
    }
  };
  1. I set the messages state which is an array of messages to en empty array along with async/await mechanism. The getMessages function fetches the messages of new channel. This way messages belonging to preceding group are cleared and there is no accumulation of messages in the UI.
  switchSession = async () => {
    await this.setState({ messages: [] });
    await this.getMessages();
  }
Sujay
  • 570
  • 3
  • 8
  • 24