DEV Community

Cover image for Building a SOLID Pokémon Game in React: A Developer’s Adventure!
vigneshiyergithub
vigneshiyergithub

Posted on

Building a SOLID Pokémon Game in React: A Developer’s Adventure!

S: Single Responsibility - One Pokémon, One Job

Problem: PokemonComponent handles catching, battling, and displaying scores, violating SRP.

function PokemonComponent({ pokemon, onCatch, onBattle, score }) {
  return (
    <div>
      <h2>{pokemon.name}</h2>
      <button onClick={() => onCatch(pokemon)}>Catch</button>
      <button onClick={() => onBattle(pokemon)}>Battle</button>
      <div>Score: {score}</div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Solution: Split responsibilities.

function PokemonCatcher({ pokemon, onCatch }) {
  return <button onClick={() => onCatch(pokemon)}>Catch</button>;
}
function PokemonBattler({ pokemon, onBattle }) {
  return <button onClick={() => onBattle(pokemon)}>Battle</button>;
}
function ScoreBoard({ score }) {
  return <div>Score: {score}</div>;
}
function PokemonGame({ pokemon, onCatch, onBattle, score }) {
  return (
    <div>
      <h2>{pokemon.name}</h2>
      <PokemonCatcher pokemon={pokemon} onCatch={onCatch} />
      <PokemonBattler pokemon={pokemon} onBattle={onBattle} />
      <ScoreBoard score={score} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

O: Open/Closed - Evolving Pokémon Components

Problem: Adding features like power-ups requires modifying existing components.

Solution: Use a Higher-Order Component (HOC).

function withPowerUp(PokemonComponent) {
  return function PoweredUpComponent(props) {
    const [isPoweredUp, setPowerUp] = useState(false);
    const powerUp = () => {
      setPowerUp(true);
      setTimeout(() => setPowerUp(false), 5000);
    };
    return (
      <div>
        <PokemonComponent {...props} isPoweredUp={isPoweredUp} />
        <button onClick={powerUp}>Power Up!</button>
      </div>
    );
  };
}

const Charmander = ({ isPoweredUp }) => (
  <div>Charmander {isPoweredUp && "(Powered Up!)"}</div>
);

const PoweredCharmander = withPowerUp(Charmander);

function PokemonApp() {
  return <PoweredCharmander />;
}
Enter fullscreen mode Exit fullscreen mode

L: Liskov Substitution - The Interchangeable Pokémon

Problem: Swapping components causes issues.

Solution: Use a base component.

function BasePokemon({ attack, children }) {
  return (
    <div className="pokemon">
      <div>Attack: {attack}</div>
      {children}
    </div>
  );
}
function Pikachu({ attack }) {
  return (
    <BasePokemon attack={attack}>
      <h2>Pikachu</h2>
    </BasePokemon>
  );
}
function Charizard({ attack }) {
  return (
    <BasePokemon attack={attack}>
      <h2>Charizard</h2>
    </BasePokemon>
  );
}
function PokemonBattle() {
  return (
    <div>
      <BasePokemon attack="Tackle">
        <h2>Generic Pokémon</h2>
      </BasePokemon>
      <Pikachu attack="Thunderbolt" />
      <Charizard attack="Flamethrower" />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

D: Dependency Inversion - Depend on Abstractions

Problem: Components tightly coupled with data sources.

Solution: Use context for data injection.

const PokemonContext = createContext();
function Pikachu() {
  const { attack } = useContext(PokemonContext);
}
<PokemonContext.Provider value={{ attack: "Thunderbolt" }}>
  <Pikachu />
</PokemonContext.Provider>
Enter fullscreen mode Exit fullscreen mode

Cheatsheet: SOLID Principles

Principle Poké-Mantra Trainer’s Tip
Single Responsibility One Pokémon, one role. Split complex components into focused ones.
Open/Closed Evolve without changing. Use HOCs, render props for new features.
Liskov Substitution Components like Pokémon moves - interchangeable. Ensure components can be used interchangeably.
Dependency Inversion Depend on abstractions, not concretes. Use context or props for data management.

Top comments (0)