2

For some reason, only the last element of my messages state array is rendering. Whenever I call addMessage and print the current state of messages, an empty array always prints out. After I push the new message to the state array, the array prints out with one message. Why is my messages state not properly saving and resetting to an empty array?

const ChatApp = () => {
  const [messages, setMessages] = React.useState([]);

  useEffect(() => {
    ioClient.emit('join', { roomid: roomId });
    ioClient.emit('connected', { roomid: roomId });
  }, []);

  ioClient.on('message', (msg) => {
    const messageObject = {
      username: msg.from,
      message: msg.body,
      timestamp: msg.timestamp
    };
    addMessage(messageObject);
  });

  ioClient.on('send', (con) => {
    for (var key in con.arra) {
      var value = con.arra[key];
      const messageObject = {
        username: value.from,
        message: value.body,
        timestamp: value.timestamp
      };
      addMessage(messageObject);
    }
  });

  const sendHandler = (message) => {
     var res = moment().format('MM/DD/YYYY h:mm a').toString();
     ioClient.emit('server:message', {
       from: senderFullName,
       body: message,
       timestamp: res,
       roomId: roomId
     });
  };

  const addMessage = (message) => {
    console.log(messages);
    let messagess = [...messages, message];
    setMessages(messagess);
    console.log(messagess);
  };

  return (
    <div className="landing">
      <Container>
        <Row className="mt-5">
          <Col md={{ span: 8, offset: 2 }}>
            <Card style={{ height: '36rem' }} border="dark">
              <Messages msgs={messages} />
              <Card.Footer>
                <ChatInput onSend={sendHandler}></ChatInput>
              </Card.Footer>
            </Card>
          </Col>
        </Row>
      </Container>
    </div>
  );
};

ChatApp.defaultProps = {
  username: 'anonymous'
};

const mapStateToProps = (state) => {
  return {
    authUser: state.auth.user,
    profile: state.profile.profile
  };
};

export default connect(mapStateToProps)(ChatApp);

Messages Component

import React from 'react';

import Message from './Message';

class Messages extends React.Component {
  componentDidUpdate() {
    // There is a new message in the state, scroll to bottom of list
    const objDiv = document.getElementById('messageList');
    objDiv.scrollTop = objDiv.scrollHeight;
  }

  render() {
    // Loop through all the messages in the state and create a Message component
    const messages = this.props.msgs.map((message, i) => {
        return (
          <Message
            key={i}
            username={message.username}
            timestamp={message.timestamp}
            message={message.message}
            fromMe={message.fromMe} />
        );
      });

    return (
      <div className='messages' id='messageList'>
        { messages }
      </div>
    );
  }
}

Messages.defaultProps = {
  msgs: []
};

export default Messages;

Message Component

import React from 'react';
import Container from 'react-bootstrap/Container';
import Image from 'react-bootstrap/Image';
import Card from 'react-bootstrap/Card';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Button from 'react-bootstrap/Button';
import Media from 'react-bootstrap/Media';

class Message extends React.Component {
  render() {
    let now = this.props.timestamp;

    return (

      <ul className="list-unstyled">
              <Media.Body>
                <h6 className="font-weight-bold">{ this.props.username }</h6>
                <p>
                { this.props.message }
              </p>
                <p className="small text-muted">
                  {now}
              </p>
              </Media.Body>
            </Media>
      </ul>
    );
  }
}

Message.defaultProps = {
  message: '',
  username: '',
  to: '',
  fromMe: false
};

export default Message;
asdfcoder
  • 123
  • 1
  • 9

2 Answers2

4

I replaced the line "addMessage(messageObject);`" with:

setMessages((previousMessages) => [messageObject, ...previousMessages]);

By passing a callback function to setMessages, I avoid using the state object from outside.

asdfcoder
  • 123
  • 1
  • 9
0

You are making event handlers on each render. So before even the first message you have 2 different functions respond to an each event since a function component is run at least two times before the first actual rendering. The state may be lost within those callbacks.

While this may not be the solution, it will definitely remove unwanted behaviour: put your binding callbacks ioClient.on into the useEffect

yrden
  • 31
  • 2
  • the same problem still occurs – asdfcoder Jul 26 '20 at 00:50
  • @asdfcoder we may need to see code of your `sendHandler` function to deduce the problem – yrden Jul 26 '20 at 00:56
  • @asdfcoder the only thing that comes to mind, is if you are refreshing the browser page after each message. That way `messages` never has more than one item. Otherwise everything is looking fine and the state should update properly – yrden Jul 26 '20 at 01:23
  • As far as I know, the browser page is not refreshing after each message. – asdfcoder Jul 27 '20 at 01:03