'Redux Saga with socket.io stacked loops make data duplication on redux state

I'm currently building a simple chat feature in my app using redux saga. Chat successfully entered from the emit server to the client

But the problem is, so when I move from the chat page and then back to the chat page, the last chat data sent will be duplicated to 2. Then when I move again and return to the chat page, the sent chat will be 3 and so on.

i think this happened maybe because the listener loop on fetchKonsultasi() function was stacked

Here is a dispatch function onGetKonsultasi() to listen to emits and onGetListKonsultasi() to retrieve chat list data from the server. Chat data is stored in the dataListKonsultasi, when there is an emit from the server, the dataListKonsultasi state will be updated

Someone help me, I really appreciate your help

index.js

useEffect(() => {
    onGetListKonsultasi()
    onGetKonsultasi()
}, [])

------

Konsultasi.propTypes = {
  dataKonsultasi: PropTypes.object,
  dataListKonsultasi: PropTypes.object,
  onGetKonsultasi: PropTypes.func,
  onGetListKonsultasi: PropTypes.func,
}

const mapStateToProps = ({ konsultasiReducer }) => ({
  dataKonsultasi: konsultasiReducer.dataKonsultasi,
  dataListKonsultasi: konsultasiReducer.dataListKonsultasi,
})

const mapDispatchToProps = dispatch => ({
  onGetKonsultasi: () => dispatch(getKonsultasi()),
  onGetListKonsultasi: () => dispatch(getListKonsultasi()),
})

export default connect(mapStateToProps, mapDispatchToProps)(Konsultasi)

saga.js

function createSocketConnection(url) {
  return io(url)
}

function createSocketChannel(socket) {
  return eventChannel(emit => {
    const eventHandler = event => {
      emit(event)
    }

    const errorHandler = errorEvent => {
      emit(new Error(errorEvent.reason))
    }

    socket.on("AdminReceiveMessage", eventHandler)

    socket.on("error", errorHandler)

    const unsubscribe = () => {
      socket.off("AdminReceiveMessage", eventHandler)
    }

    return unsubscribe
  })
}

function* fetchKonsultasi() {
  const socket = yield call(createSocketConnection, env.SOCKET_URL)
  const socketChannel = yield call(createSocketChannel, socket)

  while (true) {
    try {
      const response = yield take(socketChannel)
      yield put(updateListKonsultasiSuccess(response))
    } catch (err) {
      console.log("socket error: ", err)
    }
  }
}

function* fetchListKonsultasi() {
  try {
    const response = yield call(getListKonsultasi)
    yield put(getListKonsultasiSuccess(response))
  } catch (error) {
    yield put(getListKonsultasiFail(error))
  }
}

export function* watchSocket() {
  yield takeEvery(GET_KONSULTASI, fetchKonsultasi)
  yield takeEvery(GET_LIST_KONSULTASI, fetchListKonsultasi)
}

function* konsultasiSaga() {
  yield all([fork(watchSocket)])
}

reducer.js

case GET_LIST_KONSULTASI_SUCCESS:
  return {
    ...state,
    dataListKonsultasi: action.payload,
  }


case UPDATE_LIST_KONSULTASI_SUCCESS:
  return {
    ...state,
    dataListKonsultasi: {
      users: { ...state.dataListKonsultasi.users },
      data: [...state.dataListKonsultasi.data, action.payload],
    },

dataListKonsultasi Structure :

{
    "users": {
      ......
    },
    "data": [
        {
           ..chat_data...
        },
        {
           ..chat_data...
        },
    ]
}


Solution 1:[1]

acutely the problem come from when you change tab with your router you didn't lost your data that's you add into your store in socket connection, then each time you come into your page you get new data and combine last data with new data and that's happening.

good way to solve this problem is, create an action to clear data list from store and call this action inside of closing socket connection, by this approach every time you lose connection with your socket you clear data and re write data with connecting again.

case CLEAR_LIST:
  return {
    ...state,
    dataListKonsultasi: {
        users: {},
        data: [],
    },
 }

and then

socket.on("disconnect", () => {
  // call your clear action here
});

Solution 2:[2]

Hey there Muhammand so over here is where I think might help you out

case UPDATE_LIST_KONSULTASI_SUCCESS:
  return {
    ...state,
    dataListKonsultasi: {
      users: { ...state.dataListKonsultasi.users },
//Here you are always spreading the messages in the store state and 
//Then what is happening here is you are adding all the data fetched 
// previous data + action.payload from the server so every time you load 
//the messages page youre going to be adding a new batch of messages to 
//the store state
      data: [...state.dataListKonsultasi.data, action.payload],
    },

I think try creating a action for clearing your messages data every time you unmount the messages page from the store then whenever you come back to the messages page it should be good Let me know if this helps :D

e.g:

case CLEAR_DATA_LIST: {
return {
...state,
dataListKonsultasi: {
  ...state.dataListKonsultasi,
  data: [],
},

Then either call this when unmounting or in your socket.disconnect and if you want to clear the users data too just handle that according to what you want to acheive :D Might be cool to keep the users data and not clear it then you can always show the amount of users online etc

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mahdi ashori
Solution 2