0

I'm new to React and am currently trying to create a real time chat app. I'm nearly finished. the last feature needed is that the React component pasted below needs to receive messages from the Django channels ChatConsumer and update the chatHistory state with the new message. For some reason when handleSubmit is triggered, ws.current.onmessage sends the same message object to the front end twice. This results in an error when my return statement tries to map the chatHistory and finds two objects with the same key.

I'm wondering if this is due to Strict Mode being enabled in development. Is that the issue, and if so, how do I bypass it? Otherwise, what else can I improve about this component to get things running?

import React, { useEffect, useRef, useState } from "react";

import axios from "axios";

function ChatRoom({ serverURL, recipientId }) {
  let ready = false;
  let latestMessageId = null;
  const ws = useRef(null);

  const [loading, setLoading] = useState(true);
  const [newMessageLoading, setNewMessageLoading] = useState(false);
  const [error, setError] = useState(null);

  const [currentUser, setCurrentUser] = useState();
  const [recipient, setRecipient] = useState();
  const [chatroom, setChatroom] = useState();
  const [chatHistory, setChatHistory] = useState([]);
  const [message, setMessage] = useState("");

  const token = localStorage.getItem("Token");

  const fetchData = async () => {
    try {
      const currentUserResponse = await axios.get(
        `http://${serverURL}/user/token/${token}`
      );
      setCurrentUser(currentUserResponse.data);

      const recipientResponse = await axios.get(
        `http://${serverURL}/user/id/${recipientId}`
      );
      setRecipient(recipientResponse.data);

      const chatroomResponse = await axios.get(
        `http://${serverURL}/chatroom/${token}/${recipientId}`
      );
      setChatroom(chatroomResponse.data);

      const messagesResponse = await axios.get(
        `http://${serverURL}/chatroom/${chatroomResponse.data.id}/messages`
      );
      setChatHistory(messagesResponse.data);

      setLoading(false);
    } catch (error) {
      console.error("Error fetching data: ", error);
      setError(error);
      setLoading(false);
    }
  };

  const handleNewChat = async () => {
    axios
      .get(`http://${serverURL}/test_token`, {
        headers: {
          Authorization: `Token ${token}`,
        },
      })
      .then((response) => {
        if (response.data.split(" ")[0] === "Passed") {
          try {
            ws.current = new WebSocket(
              `ws://${serverURL}/ws/socket-server/?token=${token}&recipient=${recipientId}`
            );

            ws.current.onopen = () => {
              console.log("Websocket connection established.");
            };

            ws.current.onmessage = (event) => {
              const newMessage = JSON.parse(event.data);
              const prevMessages = [];

              setChatHistory((prevChatHistory) => [
                ...prevChatHistory,
                newMessage.message,
              ]);

              setNewMessageLoading(false);
            };
          } catch (error) {
            console.error(error);
          }
        }
      })
      .catch((error) => {
        setError(error);
        console.error(error);
      });
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    setNewMessageLoading(true);
    if (ws.current && ws.current.readyState === WebSocket.OPEN) {
      ws.current.send(
        JSON.stringify({
          token: token,
          recipientId: recipientId,
          message: message,
        })
      );
    }
    setMessage("");
  };

  useEffect(() => {
    if (ready) {
      fetchData();
      handleNewChat();
      ready = false;
    }
    ready = true;
  }, []);

  return (
    <>
      {console.log(newMessageLoading)}
      {loading || newMessageLoading ? (
        <p className="loading">Loading...</p>
      ) : (
        <div className="chatroom">
          <div className="chat-title">
            <h3>Chat with Brad</h3>
          </div>
          <div className="feed">
            {chatHistory.map((message) => (
              <div
                className={
                  currentUser.id === message.sender ? "message-user" : "message"
                }
                key={message.id}
              >
                <div className="message-content">
                  <h4>
                    {currentUser.id === message.sender
                      ? "You"
                      : recipient.username}
                  </h4>
                  <p>{message.content}</p>
                </div>
              </div>
            ))}
          </div>
          <div className="message-form">
            <form onSubmit={handleSubmit}>
              <input
                autoFocus
                className="message-input"
                type="text"
                value={message}
                onChange={(e) => setMessage(e.target.value)}
              />
              <input className="send-btn" type="submit" value="Send" />
            </form>
          </div>
        </div>
      )}
    </>
  );
}

export default ChatRoom;
1
  • Yes, Strict Mode renders components twice in development mode only. Commented Apr 19 at 20:45

0

Browse other questions tagged or ask your own question.