Tips for displaying components based on global state

Viewed 27

I'm build game interface that has the following user flow:

user lands on one of the games URL eg. www.name.com/game1, first gets intro screen, than game screen and finally fail or success screen.

I'm trying to figure out the most optimal way to do this. Bellow is the code that works just fine but I'm looking for more elegant and scale-able solution. Any idea?

import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";

// Import views and components
import Step1 from "../Intro/Step1";
import StatusBar from "../../components/StatusBar/StatusBar";
import Game1 from "./Games/Game1/Game1";
import Game2 from "./Games/Game2/Game2";
import Intro from "./Intro/Intro";
import Password from "./Password/Password";
import Success from "./Success/Success";
import Fail from "./Fail/Fail";
import FailBeginOnStart from "./Fail/FailBeginOnStart";

// Data

function Game() {
  const data = {
    game1: {
      desc: "some description for game 1",
    },
    game2: {
      desc: "some description for game 2",
    },
  };

  // Get global states from redux toolkit
  const showIntro = useSelector((state) => state.game.showIntro);
  const showSuccess = useSelector((state) => state.game.showSuccess);
  const showFail = useSelector((state) => state.game.showFail);
  const showPassword = useSelector((state) => state.game.showPassword);
  const completedGame = useSelector((state) => state.game.completedGame);
  const selectedLanguage = useSelector((state) => state.game.selectedLanguage);

  // Get current param from URL (example /game1)
  const { game } = useParams();

  // Strip slash to get matching game ID (example game1)
  const gameId = game.replace(/-/g, "");

  const GameScreen = () => {

    // show intro screen
    if (showIntro === true) {
      return (
        <>
          <StatusBar />
          <Intro path={game} id={gameId} data={data[gameId]} />
        </>
      );
    }

    // show success screen
    if (showSuccess === true) {
      return (
        <>
          <StatusBar />
          <Success data={data[gameId]} />
        </>
      );
    }

    // show fail screen
    if (showFail === true) {
      return (
        <>
          <StatusBar />
          <Fail data={data[gameId]} />
        </>
      );
    }

    // Show actual game
    switch (true) {
      case game === "game1":
        return <Game1 data={data[gameId]} />;
      case game === "game2":
        return <Game2 data={data[gameId]} />;
      default:
        return <Step1 />;
    }
  };

  return <GameScreen />;
}

export default Game;
1 Answers

I'd suggest React Router and changing to class based components for your use case. You'd do a BaseGame class as a template for the concrete games. (if you're using typescript you can make it an abstract class.).

These examples are without further information on the actual flow of your page so you might need to adjust it.

class BaseGame extends React.Component {
    // creating dummy members that get overwritten in concrete game classes
    desc = "";
    id = 0;
    data = {}

    constructor(){
        this.state={
            intro: true,
            success: false,
            fail: false
        }
    }

    /** just dummy functions as we don't have access to
    /*  abstract methods in regular javascript.
    /*
    */
    statusBar(){ return <div>Statusbar</div>}
    gameScreen(){ return <div>the Game Screen</div>}
    
    render(){
         return (
             {this.statusBar()}
             {if(this.state.intro) <Intro data={this.data} onStart={() => this.setState({intro: false})}/>}
             {if(this.state.success) <Success data={this.data}/>}
             {if(this.state.fail) <Faildata={this.data}/>}
             {if(!this.state.intro && !this.state.fail && !this.state.success) this.gameScreen()} 
         )
    }
}

class Game1 extends BaseGame {
    id = 1;
    data = {GameData}

    // actual implementation of the screens
    statusBar(){ return <div>Game1 StatusBar</div>}
    gameScreen(){ return (
        <div>
            <h1>The UI of Game 1</h1>
            Make sure to setState success or failure at the end of the game loop
        </div>
    )}
}

and in your app function

const App = () => (
 <Router>
    <Switch>
      <Route exact path="/" component={Step1} />
      <Route path="/game1" component={Game1} />
      <Route path="/game2" component={Game2} />
      ...
    </Switch>
  </Router>
)

just a quick idea. You propably want to add a centralized Store like Redux depending where you wanna manage the State. But the different Game Classes can very well manage it on their own when they don't need a shared state. Also depending what the statusBar does it can be outside of the game and just in your app function

...
<StatusBar />
<Router>
... etc.
</Router>

in your app function.

Related