-1

I'm working on a React project using WebSockets to receive real-time updates. However, I'm encountering an issue where my component's state resets to the default value every time a WebSocket message is received. I've been trying to debug this, but haven't figured out why this is happening. Thank you for your help in advance :).

Reducer

const initialState = {
  name: "",
  message: "",
  word: "",
  color: "#000000",
  eraseMode: false,
  strokeWidth: 2,
  eraserWidth: 10,
  isDrawing: false,
  id: null,
  turnID: null,
  names: null,
  scores: null,
  msgArr: [],
}

function reducer(state, action) {
  switch (action.type) {
    case "setAll":
      return {
        ...state,
        id: action.payload.id,
        turnID: action.payload.turnID,
        names: action.payload.names,
        scores: action.payload.scores,
      }
    case "setName":
      return { ...state, name: action.payload }
    case "setMessage":
      return { ...state, message: action.payload }
    case "setWord":
      return { ...state, word: action.payload }
    case "setColor":
      return { ...state, color: action.payload }
    case "setErasemode":
      return { ...state, eraseMode: action.payload }
    case "setStrokeWidth":
      return { ...state, strokeWidth: action.payload }
    case "setEraserWidth":
      return { ...state, eraserWidth: action.payload }
    case "setIsDrawing":
      return { ...state, isDrawing: action.payload }

    case "setId":
      return { ...state, id: action.payload }
    case "setTurnID":
      return { ...state, turnID: action.payload }
    case "setNames":
      return { ...state, names: action.payload }
    case "setScores":
      return { ...state, scores: action.payload }
    case "addMsg":
      return { ...state, msgArr: [...state.msgArr, action.payload] }

    default:
      throw new Error()
  }
}

functional component

const [state, dispatch] = useReducer(reducer, initialState)
const ws = useRef(null)
const url = "ws://localhost:9833"
const p = useParams()

  useEffect(() => {
    // Initialize WebSocket
    const connectWebSocket = () => {
      if (ws.current) {
        ws.current.onerror = ws.current.onopen = ws.current.onclose = null
        ws.current.close()
      }
      ws.current = new WebSocket(`${url}/${p.roomID}`)
      console.log("Attempting to connect to WebSocket:", `${url}/${p.roomID}`)

      ws.current.onopen = () => {
        console.log("WebSocket connection opened")
      }

      ws.current.onerror = (error) => {
        console.error("WebSocket error:", error)
      }

      ws.current.onclose = () => {
        console.log("WebSocket connection closed")
        // Attempt to reconnect in case of a connection failure
        toast.error("Connection lost!")
        ws.current = null
        dispatch({ type: "setId", payload: null })
        setTimeout(() => connectWebSocket(), 1000)
      }

      ws.current.onmessage = (message) => {
        console.log("when WebSocket message received:", state)
        //here state is always equal to initial state
        const msg = JSON.parse(message.data)
        switch (msg.type) {
          case 0:
            if (msg.turnID === msg.id) {
              dispatch({ type: "setWord", payload: msg.word })
            }
            dispatch({
              type: "setAll",
              payload: {
                id: msg.id,
                turnID: msg.turnID,
                names: msg.names,
                scores: msg.scores,
              },
            })
            break
          case 1:
            break
          case 3:
            if (msg.isTrue) {
              dispatch({
                type: "addMsg",
                payload: `G ${state.names[msg.id]} guessed the word!đź‘Źđź‘Ź`,
              })
              dispatch({ type: "setScores", payload: msg.scores })
            } else {
              dispatch({
                type: "addMsg",
                payload: `B ${state.names[msg.id]}: ${msg.message}`,
              })
            }
            break

          case 4:
            dispatch({
              type: "addMsg",
              payload: `S ${state.names[msg.turnID]} is drawing now.`,
            })
            dispatch({ type: "setTurnID", payload: msg.turnID })
            if (msg.turnID === state.id)
              dispatch({ type: "setWord", payload: msg.word })
            break

          case 6:
            dispatch({
              type: "addMsg",
              payload: `O ${msg.name} joined the room.`,
            })
            dispatch({ type: "setScores", payload: msg.scores })
            dispatch({ type: "setNames", payload: msg.names })
            break

          case 7:
            dispatch({
              type: "addMsg",
              payload: `R ${state.names[msg.id]} left the room.`,
            })
            dispatch({ type: "setScores", payload: msg.scores })
            dispatch({ type: "setNames", payload: msg.names })
            break

          default:
            break
        }
      }
    }

    connectWebSocket()

    // Cleanup on component unmount
    return () => {
      if (ws.current) {
        ws.current.onerror = ws.current.onopen = ws.current.onclose = null
        ws.current.close()
      }
    }
  }, [p.roomID])

Initially, I was using the useState hook after that I used useReducers in both cases it happened. I have checked that after dispatch my state updates but when a message is received it again comes to the Initial state.

4
  • You need to give more context for us to help answer your questions, share your full component which also contains the state. check this on how to ask questions. stackoverflow.com/help/how-to-ask Commented Jun 25 at 6:42
  • @AkinniyiAkinbodeBolade I have added the relevant component code.
    – user25703354
    Commented Jun 25 at 7:19
  • I am still not able to deduce from the code provided. Everything seems right, but there might be something in your project that causes a re-render Commented Jun 25 at 8:06
  • @AkinniyiAkinbodeBolade I figured out if I use a state outside the JSX return statement. [ The part of the code specifies what the component should render to the DOM. ] then it resets to the initial value. Still, I don't know the reason and lost my 2 days finding a solution to this problem. Thank you for your time. : )
    – user25703354
    Commented Jun 25 at 12:19

1 Answer 1

0

This issue might be that you are not passing a dependency array to the useEffect hook, when a dependency array is not added the useEffect will run on every re-render which might cause updating the state. You can pass an empty array to the useEffect to avoid running it.

useEffect(()=>{
   const connect = ()=>{
       //code for connectiong to websocket
       ws = new websocket....

       ws.onmessage() = (message)=>{
         //here state is always initial 
         //state change code

         //after state change code it changes but when new message arrives its in the initial state 
        }
   }
},[])

If this is not the issue, you will need to provider more context on how and where you're setting the state.

2
  • adding [] is same as leaving it without it Commented Jun 25 at 7:21
  • 1
    @AkinniyiAkinbodeBolade That's not true at all. Empty dependency array equals exactly one useEffect hook callback call at the end of the initial render cycle whereas no dependency array equals the useEffect hook callback being called each and every render cycle. They are nearly opposites!
    – Drew Reese
    Commented Jun 26 at 7:17

Not the answer you're looking for? Browse other questions tagged or ask your own question.