asdfcoder
asdfcoder

Reputation: 133

Why is my state array resetting to empty?

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;

Upvotes: 2

Views: 2301

Answers (2)

asdfcoder
asdfcoder

Reputation: 133

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.

Upvotes: 4

yrden
yrden

Reputation: 31

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

Upvotes: 0

Related Questions