Danny
Danny

Reputation: 337

Receiving same message multiple times via web socket. The older messages are replaced after 3 or 4 new chat messages

I am integrating web sockets into React with Django in back-end.

I am able to send messages, and receive the new messages from the back-end.

The problem is that after sending 3 or 4 messages, the previous messages are getting replaced by the new messages in the front-end, but all is fine in the back-end, and in fact everything works normally after I refresh.

How to properly update state for situations like this?

Edit:

The main problem which I found now is, when I send a chat, the state gets updated multiple times, and its length changes everytime and the screen flutters a lot.

slice

name: 'chat',
initialState: {
        chatMessages: [],
}

reducers: {
        setChatMessages(state, action) {
            const chatmessages = action.payload;
            return {...state, chatMessages:chatmessages}; 
        },
}

chat

const chatMessagesSelector = useSelector(selectChats);
const chatMessages = chatMessagesSelector?.chatMessages

    chatSocket.onmessage = (e) => {
    var data = JSON.parse(e.data)
    var message = {message: data.message, user: data.user,
                    timestamp: data.timestamp};  
    //console.log(message)
    let updatedMessages = [...chatMessages];
    updatedMessages.push(message);
    dispatch(setChatMessages(updatedMessages));
}

This is the chatMessages object logged above the updatedMessages.

Array(1)
0: {user: "testuser", message: "check!", timestamp: "2021-03-09T09:15:09.408717+01:00}
length: 1

Upvotes: 4

Views: 3403

Answers (2)

Ajeet Shah
Ajeet Shah

Reputation: 19843

The below code (Approach 1*):

chatSocket.onmessage = (e) => {
    var data = JSON.parse(e.data)
    var message = {message: data.message, user: data.user, timestamp: data.timestamp};
    let updatedMessages = [...chatMessages];
    updatedMessages.push(message);
    dispatch(setChatMessages(updatedMessages));

reads from redux and creates new Array and pushes new element and dispatches the whole array.

Instead of that, (Approach 2), you can just send the new element to be pushed to redux store by the reducer:

chatSocket.onmessage = (e) => {
    var data = JSON.parse(e.data)
    var message = {message: data.message, user: data.user, timestamp: data.timestamp};   
    dispatch(setChatMessages(message));

And now (for Approach 2) you need to make this change in reducer:

reducers: {
   setChatMessages(state, action) {
      return {...state, chatMessages: [...state.chatMessages, action.payload]};}
}

You can also do this:

reducers: {
   setChatMessages(state, action) {
      state.chatMessages.push(action.payload)
}

because :

Redux Toolkit allows us to write "mutating" logic in reducers. It doesn't actually mutate the state because it uses the Immer library, which detects changes to a "draft state" and produces a brand new immutable state based off those changes

*Approach 1 may be buggy if chatMessages in your component is outdated or delayed.

Edit:

The real issue seems to be that your event listener is getting registered multiple times:

To fix it, you should move it into useEffect or componentDidMount:

useEffect(() => {
  chatSocket.onmessage = (e) => {
    var data = JSON.parse(e.data)
    // ...
  }
  return function cleanup() {
    // de-register the socket event
  }
}, [])

Upvotes: 2

Mr. Hedgehog
Mr. Hedgehog

Reputation: 2885

You need to concatenate new messages with old messages, but you are replacing them:

setChatMessages(state, action) {
  const chatMessages = action.payload;
  return { ...state, chatMessages: state.chatmessages.concat(chatMessages)};
}

Be careful and avoid duplicates, if you store same messages locally and then send them back from socket.

Upvotes: 0

Related Questions