skip to Main Content

I’m develepoing a chat page with a React class component. The code should append every message that I sent by the input to <MessageGroup sentByUser> block

class ChatPage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            userMessages: [[0, 'First example message'], [1, 'Second msg']],
            userMessageNextId: 2,
        };
        this.sendMessage = this.sendMessage.bind(this);
    }

    render() {
        console.log(this.state.userMessages);
        return (
            <div className="chat-page mb-5">
                <div className="messages-container">
                    <MessageGroup sentBy="Juan">
                        <Message>This is first message</Message>
                        <Message>This is second message</Message>
                    </MessageGroup>
                    <MessageGroup sentByUser>
                        {this.state.userMessages.map((val) => (<Message key={val[0]}>{val[1]}</Message>))}
                    </MessageGroup>
                </div>
                <div className="input-container">
                    <div className="input-group">
                        <input type="text" className="form-control" id="message-text" placeholder="Type something..."/>
                        <button className="btn btn-primary" onClick={this.sendMessage}>Send</button>
                    </div>
                </div>
            </div>
        )
    }

    sendMessage() {
        const messageText = document.querySelector("#message-text").value;
        if (!messageText?.trim()) return;
        this.setState(state => {
            console.log('Setting state...')
            const newArray = [...state.userMessages, [state.userMessageNextId, messageText]];
            return {
                userMessages: newArray,
                userMessageNextId: state.userMessageNextId + 1
            }
        })
    }
}

When I’m debugging my code, I see two default messages from the state. And when I send a message, I see "Setting state…" and new needed array in the console, but the UI is not changed. What’s wrong here?

2

Answers


  1. You should use controlled components in React to manage the input value instead of document.querySelector.

    class ChatPage extends Component {
        constructor(props) {
            super(props);
            this.state = {
                userMessages: [[0, 'First example message'], [1, 'Second msg']],
                userMessageNextId: 2,
                messageText: '' // Add messageText to the state
            };
            this.sendMessage = this.sendMessage.bind(this);
            this.handleInputChange = this.handleInputChange.bind(this);
        }
    
        render() {
            console.log(this.state.userMessages);
            return (
                <div className="chat-page mb-5">
                    <div className="messages-container">
                        <MessageGroup sentBy="Juan">
                            <Message>This is the first message</Message>
                            <Message>This is the second message</Message>
                        </MessageGroup>
                        <MessageGroup sentByUser>
                            {this.state.userMessages.map((val) => (
                                <Message key={val[0]}>{val[1]}</Message>
                            ))}
                        </MessageGroup>
                    </div>
                    <div className="input-container">
                        <div className="input-group">
                            <input
                                type="text"
                                className="form-control"
                                id="message-text"
                                placeholder="Type something..."
                                value={this.state.messageText} // Bind the input value to state
                                onChange={this.handleInputChange} // Add onChange event handler
                            />
                            <button className="btn btn-primary" onClick={this.sendMessage}>
                                Send
                            </button>
                        </div>
                    </div>
                </div>
            );
        }
    
        sendMessage() {
            if (!this.state.messageText.trim()) return;
            this.setState((state) => {
                console.log('Setting state...');
                const newArray = [...state.userMessages, [state.userMessageNextId, state.messageText]];
                return {
                    userMessages: newArray,
                    userMessageNextId: state.userMessageNextId + 1,
                    messageText: '' // Clear the input field after sending the message
                };
            });
        }
    
        handleInputChange(event) {
            this.setState({ messageText: event.target.value }); // Update the messageText state with the input value
        }
    }
    
    export default ChatPage;
    Login or Signup to reply.
  2. The main issue here is your use of a native DOM method to interrogate the DOM to grab the input value when really you should be keeping that value in state, and using a method to update that value whenever the input onChange event is fired. Then, when the button is clicked, you can then copy that input state value into the userMessages state.

    (Using value={this.state.input} makes the input a controlled input – changes in state are reflected back to its value when the component re-renders. It makes sure everything is sync’d, and is a more "React way" of approaching the problem.)

    Further: you’ll probably find an array of objects a little more easy to work with. Property names are far more meaningful than index values. For example you can set an id property which can serve is both the unique object identifier, and the key for the iterated messages.

    Obviously it’s ultimately your choice but I decided to use them in this example to show them working.

    const { Component } = React;
    
    class ChatPage extends Component {
        
      // Setting up the initial `userMessages` as an array of objects,
      // and an initial state for `input`
      constructor(props) {
        super(props);
        this.state = {
          userMessages: [
            { id: '1', msg: 'First example message' },
            { id: '2', msg: 'Second msg' }
          ],
          input: ''
        };
        this.sendMessage = this.sendMessage.bind(this);
        this.handleInput = this.handleInput.bind(this);
      }
    
      // When the input value is changed update its state
      handleInput(e) {
        this.setState({ input: e.target.value });
      }
    
      // When the button is clicked spread out the previous
      // state, and set `userMessages` to be the old `userMessages`
      // state with a new object added to it. The new object uses
      // the length of the current `userMessages` array to set the id,
      // and then sets its msg property to the value of the input state
      // Finally the input state is reset
      sendMessage(e) {
        this.setState(prev => {
          return {
            ...prev,
            userMessages: [
              ...prev.userMessages,
              {
                id: prev.userMessages.length + 1,
                msg: this.state.input
              }
            ]
          }
        });
        this.setState({ input: '' });
      }
    
      render() {
        return (
          <div className="chat-page mb-5">
            <div className="messages-container">
              <MessageGroup sentBy="Juan">
                <Message>This is first message</Message>
                <Message>This is second message</Message>
              </MessageGroup>
              <MessageGroup sentByUser>
                {this.state.userMessages.map(obj => {
                  return <Message key={obj.id}>{obj.msg}</Message>;
                })}
              </MessageGroup>
            </div>
            <div className="input-container">
              <div className="input-group">
                <input
                  type="text"
                  className="form-control"
                  id="message-text"
                  value={this.state.input}
                  placeholder="Type something..."
                  onChange={this.handleInput}
                 />
                <button
                  className="btn btn-primary"
                  onClick={this.sendMessage}
                >Send
                </button>
              </div>
            </div>
          </div>
        );
      }
    
    }
    
    
    function Message({ children }) {
      return <div>{children}</div>;
    }
    
    function MessageGroup({ children }) {
      return <div>{children}</div>;
    }
    
    const node = document.getElementById('root');
    const root = ReactDOM.createRoot(node);
    root.render(<ChatPage />);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.min.js"></script>
    <div id="root"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search