Search code examples
reactjsreact-context

UseContext returning only intial value null


I used Context to access my websocket deeper in the app, but when I try to access it, it will only give the initial value null. I have looked at similar problems here but none of them seem to be the same as mine.

My context file:

const { createContext, useRef, useEffect } = require('react');

// change it to match or use config file
const WS_URL = 'ws://localhost:3001';

const WebSocketContext = createContext({ socket: null });

function WebSocketProvider({ children }) {
  const computer = useRef(new URLSearchParams(document.location.search).get('computer'))
  const ws = useRef(null)
  useEffect(() => {
    ws.current = new WebSocket(`${WS_URL}/?computer=${computer.current}`);
    ws.current.onopen = () => {
      if (ws.current.readyState === 1) {
        ws.current.send(JSON.stringify({ computer: computer.current }));
      }
    };
    ws.current.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
    console.log('ws', ws.current)
    return () => {
      ws.current.close();
    };
  }, [computer]);
  return (
    <WebSocketContext.Provider value={{ socket: ws.current }} >
        {children}
    </WebSocketContext.Provider>
)};

export { WebSocketProvider, WebSocketContext };

My app file:

import React from 'react';
import { WebSocketProvider } from './WebsocketPage';
import ControlScreen from './ControlScreen';
import Screen from './Screen';

function App() {
  const computer = new URLSearchParams(document.location.search).get('computer');
  return (
    <div>
      <WebSocketProvider>
        {computer === 'control' && <ControlScreen />}
        {computer !== 'control' && <Screen computer={computer} />}
      </WebSocketProvider>
    </div>
  )}

export default App;

And where I'm trying to access it, ControlScreen

import React, { useContext, useEffect, useState } from "react";
import { WebSocketContext } from "./WebsocketPage";
import { handleControlMessages } from "./HandleMessages";
import CreateScreens from "./CreateScreens";

function ControlScreen() {
    const [consList, setConsList] = useState([]);
    const { socket } = useContext(WebSocketContext);
    console.log('socket', socket)
    useEffect (() => {
        console.log('socket', socket)
        socket.onmessage = (e) => {
            let message = JSON.parse(e.data);
            handleControlMessages(message, setConsList);
        };
        return () => {
            socket.removeEventListener('message');
        }    
    }, [socket]);

    return (
        <div id="controlScreen" >
            <h1>Control Screen</h1>
            {<CreateScreens consList={consList} />}
            
        </div>
    )
}      

export default ControlScreen;

I left in two console.log messages to show where I'm checking. In my console, it will first print "socket null" from the ControlScreen file, and then the socket from the context file. So I know the socket is being created and it works, but it's created after the screens are made. I created some fail-safes in the ControlScreen, like returning with console.log("loading") if there is no socket, so it wouldn't throw errors, but then it will just be stuck on that loading and won't use the useEffect again when socket is created.


Solution

  • So, I figured it out:

    import React, { createContext, useRef, useEffect  } from 'react';
    
    
    // change it to match or use config file
    const WS_URL = 'ws://localhost:3001';
    
    const WebSocketContext = createContext(null);
    
    function WebSocketProvider({ children }) {
      const computer = useRef(new URLSearchParams(document.location.search).get('computer'))
      const ws = useRef(new WebSocket(`${WS_URL}/?computer=${computer.current}`));
    
      useEffect(() => {
        ws.current.onopen = () => {
          if (ws.current.readyState === 1) {
            ws.current.send(JSON.stringify({ computer: computer.current }));
          }
        };
        ws.current.onerror = (error) => {
          console.error('WebSocket error:', error);
        };
        ws.current.onmessage = (e) => {
          console.log('message', e.data);
        };
        ws.current.onclose = (e) => {
          console.log(e.code);
        };
        const current = ws.current;
        return () => {
          current.close();
        };
      }, [ws]);
    
      return (
        <WebSocketContext.Provider value={{ socket: ws.current }} >
            {children}
        </WebSocketContext.Provider>
    )};
    
    export { WebSocketProvider, WebSocketContext };
    

    If I create the new Websocket inside of the useEffect, it won't work, probably because the useEffect is the last thing that will ... happen ..., after rendering. So I used the address with useRef outside of it, and put changing websocket as the condition of the useEffect.

    Left it here in case someone has a similar problem.