react router: pressing back button does not bring user back to previous page

Viewed 633

I'm working on an app with web socket io and have run into a strange issue. I have a simple app:

import { SocketIOClient } from './containers/SocketIOClient.js';
import { Home } from './screens/Home.js';
import { Game } from './screens/Game.js';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const App = () => (
  <SocketIOClient
    router={ (props) => (
      <BrowserRouter>
        <Routes>
          <Route exact path="/" element={<Home {...props} />}></Route>
          <Route path="/:lobbyId" element={<Game {...props} />}></Route>
        </Routes>
      </BrowserRouter>
    )}
  />
);

where SocketIOClient just contains the socket client object used to connect to my server. I also have a simple Home page:

import { useState, useEffect } from 'react';
import Container from 'react-bootstrap/Container';
import Button from 'react-bootstrap/Button';
import { useNavigate, useLocation } from 'react-router-dom';

import './Home.css'

export const Home = ({ socket }) => {
    const navigate = useNavigate();
    const location = useLocation();
    const [failedToJoin, setFailedToJoin] = useState(location.state ? location.state.failedToJoin : false);

    const createGame = () => {
        socket.emit('create_game');
    }

    useEffect(() => {
        socket.on('failed_to_join', () => {
            setFailedToJoin(true);
        });

        socket.on('space_available', ({ lobby_id }) => {
            navigate(`/${lobby_id}`);
        });
    // eslint-disable-next-line
    }, [socket])

    useEffect(() => {
        console.log(navigate)
    })

    return ( 
        <Container id="home-page">
                <Button
                    variant="info"
                    onClick={createGame}
                >
                    Create New Game
                </Button>
        </Container>
    );
};

When the user clicks the button, they create a Game and (assuming the space_available message is received) are navigated to:

import { useEffect, useState } from 'react';
import { useParams, useNavigate } from "react-router-dom";

export const Game = ({ socket }) => {
    const [joined, setJoined] = useState(false);
    const navigate = useNavigate();
    const params = useParams();
    const lobbyId = params.lobbyId

    useEffect(() => {
        socket.emit('join_game', { lobby_id: lobbyId, player_id: socket.id })

        socket.on('game_joined', _ => {
            setJoined(true)
        });

        socket.on('failed_to_join', () => {
            navigate('/', {state: {failedToJoin: true, lobbyId}});
        });

        return () => {
          socket.emit('back_button_pressed', {player_id: socket.id})
        }

    // eslint-disable-next-line
    }, [socket]);

    return joined ? (
        <div>
          <h1>lobby id: {lobbyId}</h1>
        </div>
    ) : null
}

The issue is, if I'm on the Home page and click the Create Game button, I'm brought to the Game page. If I press the browser back button, I go back to the Home page. If I press Create Game again, I'm brought the Game page, but strangely I now have to press the back button 2 times to return to Home.

If I instead press the browser forward button on Home after viewing Game page, I'm given a Warning: Can't perform a React state update on an unmounted component. Does anyone know what's causing me these issues? I have a feeling it's due to the useEffect with the socket working in some way I'm not understanding right.

1 Answers

In Home you are not cleaning up your socket connections. When you navigate back to Home another socket connection is established with callback function.

Return a cleanup function from the useEffect hook to unsubscribe the socket event.

useEffect(() => {
  const failToJoin = () => {
    setFailedToJoin(true);
  };

  const navigateToLobby = ({ lobby_id }) => {
    navigate(`/${lobby_id}`);
  }

  socket.on('failed_to_join', failToJoin);
  socket.on('space_available', navigateToLobby);

  return () => {
    socket.off('failed_to_join', failToJoin);
    socket.off('space_available', navigateToLobby);
  };
  // eslint-disable-next-line
}, [socket]);

Similarly in Game you will want to return a cleanup function from the useEffect hook to unsubscribe the socket event.

Related