Search code examples
reactjsconditional-rendering

How can I better conditionally render my components?


I have a lot of components being rendered based on different states that I'm using for a game. I'm currently using the method where I check the value of state with ampersands. I'm not sure if there's a different way I should do it, or if there's a more efficient cleaner way to do it.

I've looked up a few different ways but was wondering if someone could maybe give me suggestions for something that would work well with my code I have existing.

const App = () => {
  const [startPlayer, setStartPlayer] = useState("");
  const [endPlayer, setEndPlayer] = useState("");
  const [gameSelected, setGameSelected] = useState(false);
  const [gameStarted, setGameStarted] = useState(false);
  const [gameWon, setGameWon] = useState(false);
  const [winningTeam, setWinningTeam] = useState([]);
  const [gameSolved, setGameSolved] = useState(false);
  const isMobile = useMobileCheck();  

  const resetGame = () => {
    setStartPlayer("");
    setEndPlayer("");
    setGameSelected(false);
    setGameStarted(false);
    setGameWon(false);
    setGameSolved(false);
    setWinningTeam([]);
  };
  
  const setGameType = (gameType) => {
    setGameSelected(gameType);
  };
  
  const rollPlayers = (startYear, endYear) => {
    axios.get(`/api/gettwoplayers?startYear=${startYear}&endYear=${endYear}`).then((res) => {
      setStartPlayer(res.data[0]);
      setEndPlayer(res.data[1]);
    });
  };
  
  const startTheGame = () => {    
    setGameStarted(true);
  };

  const goBackToGameSelection = () => {
    setGameSelected(false);
    setGameStarted(false);
    setStartPlayer("");
    setEndPlayer("");
  };

  const userSetPlayer = (player, type) => {
    if(type === "start") setStartPlayer(player);
    if(type === "end") setEndPlayer(player);
  };

  const theGameWasWon = (history) => {
    history.push(endPlayer);
    setWinningTeam(history);
    setGameWon(true);
  };

  const solveGame = () => {
    setGameSolved(true);
    axios.get(`/api/solve?startPlayer=${startPlayer}&endPlayer=${endPlayer}`).then((res) => {
      console.log(res.data);
    })
  };

  return (
    <Container
      sx={{
        minHeight:'100vh',        
        maxWidth: "90vw!important",
      }}
    >
      {
        !gameSelected &&
        !gameStarted &&
        !gameWon &&
        <ChooseGame setGameType={setGameType} />
      }

      {
        !gameStarted &&
        !gameWon &&
        gameSelected === 'r' &&   
        <CreateRandomGame 
          rollPlayers={rollPlayers} 
          startPlayer={startPlayer} 
          endPlayer={endPlayer}
          startTheGame={startTheGame}
          goBack={goBackToGameSelection}
        />
      }

      {
        !gameStarted &&
        !gameWon &&  
        gameSelected === 's' &&
        <CreateUserGame
          startPlayer={startPlayer}
          endPlayer={endPlayer}
          userSetPlayer={userSetPlayer}
          startTheGame={startTheGame}
          goBack={goBackToGameSelection}
        />
      }
      
      {
        !gameWon &&
        gameStarted &&
        <GameScreen 
          startPlayer={startPlayer}
          endPlayer={endPlayer}
          gameWon={theGameWasWon}
          resetGame={resetGame}
          solveGame={solveGame}
        />
      }

      {
        gameWon &&
        <GameWon 
          resetGame={resetGame}
          winningTeam={winningTeam}
        />
      }
    </Container>
  );
}

export default App;

Solution

  • Two things you could try:

    Firstly, you've got a lot of boolean state - e.g. gameStarted, and a lot of it seems mutually-exclusive with other boolean state, for example gameStarted looks like it could never be true at the same time as gameWon. In situations like that, it can be a lot nicer to model the state as an enumerated type; unfortunately Javascript doesn't have them natively (look into TypeScript for a "true" enum type) but we can make do with strings:

    const MODE_STARTED = 'started'
    const MODE_SELECTED_RANDOM = 'random'
    const MODE_SELECTED_USER = 'user'
    const MODE_GAME_WON = 'won'
    ...
    const [gameMode, setGameMode] = useState(MODE_STARTED);
    ...
    

    Now rather than flipping individual booleans all over the place, you can just change your game mode ... e.g. setGameMode(MODE_SELECTED_RANDOM)

    Once you've done that, your JSX can become cleaner too:

    const showCorrectUI = () => {
      switch (gameMode) {
        case MODE_STARTED:
          return <GameScreen {foo} />
        case MODE_GAME_WON:
          return <GameWon {bar} />
         ... // etc
      }
    }
    
    return (
      <Container
        sx={{
          minHeight:'100vh',        
          maxWidth: "90vw!important",
        }}
      >
        {showCorrectUI()}
      </Container>)