Search code examples
node.jsreactjssocket.ioreact-hooksuse-effect

socket connect event inside useEffect does not work when component is created but works after refreshing the page in React


I want to pass a single socket instance to all my react components and hence I am connecting a socket inside the useEffect of App.js which contains all components and keeping my listeners inside useEffect of Navigation Bar component. The useEffect gets fired but socket does not get connected when the app is opened for the first time.But as I refresh the page the socket gets connected and is being passed to the components I want easily.

App.js

function App() {

  const authCtx = useContext(AuthContext)
  const username = authCtx.username
  const [socket, setSocket] = useState(null);

  const [user, setUser] = useState("");

  useEffect(() => {
    setSocket(io("http://localhost:7000"));

  }, []);

  useEffect(() => {
    socket?.emit("join_room",{
            chatroom:username
            });
      
  }, [socket, user]);

  return (
    <div>

      {authCtx.isLoggedIn && socket && <MainNavigation socket={socket} />}
      {authCtx.isLoggedIn && <FloatingBtn/>}
      <Switch>
        {authCtx.isLoggedIn && socket && <Route exact path='/Chat'>
          <Chat socket={socket} />
        </Route>}   
        
      </Switch>
    </div>
  );
}

export default App;

Solution

  • Note that in useEffect the dependency is the authContext.username, not the user.

    useEffect(() => {
        if(authCtx.username && socket){
            socket?.emit("join_room", {
              chatroom: authCtx.username
            });
        }
      }, [socket, authCtx.username]);
    

    To have a global socket instance you could create a SocketContext like:

    import { createContext, useContext } from "react";
    
    const SocketContext = createContext({
      socket: undefined
    });
    export const SocketProvider = ({ childern }) => {
      const authCtx = useContext(AuthContext);
    
      const [socket, setSocket] = useState(null);
    
      useEffect(() => {
        setSocket(io("http://localhost:7000"));
      }, []);
    
      useEffect(() => {
    
        if(authCtx.username && socket){
            socket?.emit("join_room", {
              chatroom: authCtx.username
            });
        }
    
      }, [socket, authCtx.username]);
      return (
        <SocketContext.Provider value={{ socket }}>
          {childern}
        </SocketContext.Provider>
      );
    };
    
    export default SocketContext;
    
    

    Define it in your index.js:

    import ReactDOM from "react-dom";
    import { SocketProvider } from "../SocketContext";
    
    import App from "./App";
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <AuthProvider>
        <SocketProvider>
          <App />
        </SocketProvider>
      </AuthProvider>,
      rootElement
    );
    
    

    And use it in every component like:

    import React, { useContext } from "react";
    import SocketContext from "../SocketContext";
    
    const RandomComponent = () => {
      const { socket } = useContext(SocketContext);
    
      React.useEffect(() => {
        console.log(socket);
      }, [socket]);
      return <div></div>;
    };
    
    

    Without passing it as a prop.