skip to Main Content

I’ve seen many questions about this but none that work for me. Basically, I have a GameScreen component, where when the game starts, I want the URL path to change from "/roomcode/lobby" to "/roomcode/start".

const [roomCode, setRoomCode] = useState("");
const navigate = useNavigate();

useEffect(() => {
    socket.on("game_has_started", (info) => {
      navigate(`/${roomCode}/start`);
    });
  }, [socket]);

However, my page refreshes whenever the game starts, and thus the roomCode state resets to "", and the page goes to "//start", which breaks the app. How can I stop roomCode state from resetting when navigating?

edit: roomCode is 100% set to a non "" value before the "game_has_started" event is emmitted.

3

Answers


  1. The issue here is that you are using useState hook. Note that useState hooks are only available to a component. It’s like a local variable. So, when you navigate to a new component, it’s getting unmounted. And, that’s why it goes from ‘/roomcode/lobby’ to ‘//start’.
    So, I suggest you to use localstorage to achieve your desired outcome which is persistence.

    // put this code where you are generating the new room code
    // replace newRoomCode with the new room code generated by your logic
    localStorage.setItem('roomCode',newRoomCode)
    
    // then change your useEffect hook as follows
    useEffect(() => {
        const roomCode = localStorage.getItem('roomCode');
        socket.on("game_has_started", (info) => {
            navigate(`/${roomCode}/start`);
        });
    }, [socket]);
    

    Hopefully that should solve the issue!

    Login or Signup to reply.
  2. Issue

    At a minimum you have a stale Javascript Closure issue in the "game_has_started" event handler.

    A closure is the combination of a function bundled together (enclosed)
    with references to its surrounding state (the lexical environment). In
    other words, a closure gives you access to an outer function’s scope
    from an inner function. In JavaScript, closures are created every time
    a function is created, at function creation time.

    The initial roomCode state value "" is closed over in the "game_has_started" event handler callback scope and won’t ever change/update until the socket dependency updates, in which case it will only re-enclose only the current roomCode state value at that moment in the lifecycle.

    Solution

    Since the callback is pretty light you could likely get by with including the roomCode state in the useEffect hook’s dependency array, and return a cleanup function to remove the previous handler.

    Example:

    const [roomCode, setRoomCode] = useState("");
    const navigate = useNavigate();
    
    useEffect(() => {
      const gameStartHandler =  () => {
        navigate(`/${roomCode}/start`);
      };
    
      socket.on("game_has_started", gameStartHandler);
    
      return () => {
        socket.off("game_has_started", gameStartHandler);
      };
    }, [roomCode, socket]);
    

    If the callback needed to do substantially more work or you just don’t want to continually add/remove the handlers then an alternative is to cache the roomCode state into a React ref that can be mutated and referenced at any time in the component lifecycle.

    Example:

    const [roomCode, setRoomCode] = useState("");
    const roomCodeRef = React.useRef(roomCode);
    const navigate = useNavigate();
    
    useEffect(() => {
      roomCodeRef.current = roomCode;
    }, [roomCode]);
    
    useEffect(() => {
      const gameStartHandler =  () => {
        navigate(`/${roomCodeRef.current}/start`);
      };
    
      socket.on("game_has_started", gameStartHandler);
    
      return () => {
        socket.off("game_has_started", gameStartHandler);
      };
    }, [socket]);
    
    Login or Signup to reply.
  3. useNavigate offers an optional replace parameter. By default, this parameter is set to false, which performs a "push" navigation.
    This creates a new history entry. If the user clicks the back button, they’ll navigate back to the previous page.
    Setting replace to true will replace the current history entry instead of creating a new one.
    This can be a way to prevent the page from appearing to reload because the user stays within the same history entry.

    Must go through with this https://reactrouter.com/en/main/hooks/use-navigate
    Documentation of react router Dom.
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search