Basic React Design Patterns: A practical look with Tic Tac Toe

Basic React Design Patterns: A practical look with Tic Tac Toe

TLDR:

This article explores React design patterns through a Tic Tac Toe game implemented with TypeScript, covering:

Functional Components with TypeScript
Hooks for State Management
Prop Typing for Type Safety
Composition of Components
Container and Presentational Components
Stateful and Stateless Components
Higher-Order Components (HOCs)
Render Props

These patterns enhance:

Type safety
Code organization
Reusability
Maintainability
Separation of concerns
Testability of React applications

Introduction

React, combined with TypeScript, offers a powerful toolkit for building robust web applications. Let’s explore both fundamental and advanced React patterns using a Tic Tac Toe game as our example, providing simple explanations for each pattern.

You can reference the code here while going through the article for more clarity: github

Project Structure

Our Tic Tac Toe project is organized like this:

tic-tac-toe
├── src/
│ ├── components/
│ │ ├── ui/ # Reusable UI components
│ │ ├── Board.tsx # Tic Tac Toe board
│ │ ├── Game.tsx # Game component
│ │ ├── Square.tsx # Square component
│ │ └── Score.tsx # Score component
│ ├── App.tsx # Main app component
│ └── main.tsx # Entry point
| … # more config files

Design Patterns and Implementation:

1. Functional Components and Prop Typing:

Functional components with explicitly typed props ensure type safety and self-documenting code.

// Square.tsx
export default function Square(
{ value, onClick }: { value: string | null, onClick: () => void }
) {
return (
<button className={styles.square} onClick={onClick}>
{value}
</button>
);
}

ELI5: Imagine you’re building with special Lego bricks. Each brick (component) has a specific shape (props) that only fits in certain places. TypeScript is like a magic ruler that makes sure you’re using the right bricks in the right spots.

2. Composition:

Building complex UIs from smaller, reusable components promotes modularity and reusability.

// Board.tsx
export default function Board({board, handleClick}: { board: (null | string)[], handleClick: (index: number) => void }) {
return (
<div className={styles.board_items}>
{board.map((value, index) => (
<Square key={index} value={value} onClick={() => handleClick(index)} />
))}
</div>
);
}

ELI5: Think of it like making a sandwich. The Board is the whole sandwich, and each Square is like a slice of cheese. We put many cheese slices together to make our sandwich, just like we use many Squares to make our Board.

3. State Management with Hooks:

Using useState and useEffect hooks simplifies state management in functional components.

// Game.tsx
const [board, setBoard] = useState<(null | string)[]>(Array(9).fill(null));
const [currentPlayer, setCurrentPlayer] = useState(X);
const [gameOver, setGameOver] = useState(false);

useEffect(() => {
if (aiOpponent && currentPlayer === O && !gameOver) {
const timer = setTimeout(makeAIMove, 500);
return () => clearTimeout(timer);
}
}, [board, currentPlayer]);

ELI5: Hooks are like magic spells. useState is a spell that helps our game remember things (like whose turn it is). useEffect is a spell that watches for changes and does something when they happen (like making the computer take its turn).

4. Container and Presentational Components:

This pattern separates logic (containers) from rendering (presentational components).

// Game.tsx (Container Component)
export default function TicTacToe({ aiOpponent = false }) {
const [board, setBoard] = useState<(null | string)[]>(Array(9).fill(null));
const [currentPlayer, setCurrentPlayer] = useState(X);
// … game logic

return (
<div className={styles.wrapper}>
<Board board={board} handleClick={handleClick} />
<Score x_wins={xWins} o_wins={oWins} />
</div>
);
}

// Board.tsx (Presentational Component)
export default function Board({board, handleClick}) {
return (
<div className={styles.board_items}>
{board.map((value, index) => (
<Square key={index} value={value} onClick={() => handleClick(index)} />
))}
</div>
);
}

ELI5: Imagine you’re putting on a puppet show. The Container Component is like the puppeteer who controls everything behind the scenes. The Presentational Component is like the puppet that the audience sees. The puppeteer (Container) does all the thinking and moving, while the puppet (Presentational) just shows what it’s told to show.

5. Stateful and Stateless Components:

This concept involves minimizing the number of stateful components to simplify data flow. In our implementation, TicTacToe is stateful (manages game state), while Square, Board, and Score are stateless (receive data via props).

ELI5: Think of a stateful component like a teacher in a classroom. The teacher (TicTacToe) keeps track of everything that’s happening. The students (Square, Board, Score) just do what they’re told without keeping track of anything themselves.

6. Higher-Order Components (HOCs):

HOCs are functions that take a component and return a new component with additional props or behavior.

function withAIOpponent(WrappedComponent) {
return function(props) {
const [aiEnabled, setAiEnabled] = useState(false);

return (
<>
<label>
<input
type=checkbox
checked={aiEnabled}
onChange={() => setAiEnabled(!aiEnabled)}
/>
Play against AI
</label>
<WrappedComponent {…props} aiOpponent={aiEnabled} />
</>
);
}
}

const TicTacToeWithAI = withAIOpponent(TicTacToe);

ELI5: Imagine you have a toy car. An HOC is like a special machine that can add cool features to your toy car, like flashing lights or a remote control. You put your car into the machine, and it comes out with new superpowers!

7. Render Props:

This pattern involves passing rendering logic as a prop to a component.

function GameLogic({ render }) {
const [board, setBoard] = useState(Array(9).fill(null));
// … game logic

return render({ board, handleClick });
}

function App() {
return (
<GameLogic
render={({ board, handleClick }) => (
<Board board={board} handleClick={handleClick} />
)}
/>
);
}

ELI5: This is like having a coloring book where you can choose different crayons. The coloring book (GameLogic) gives you the outline, but you get to decide what colors to use (how to render it) each time you use it.

Conclusion:

By implementing these design patterns in our TypeScript-based Tic Tac Toe game, we’ve created a modular, type-safe, and maintainable application. These patterns promote:

Clean and efficient code
Improved readability
Enhanced scalability
Better separation of concerns
Increased reusability
Easier testing and debugging

As you build more complex React applications, these patterns will serve as valuable tools in your development toolkit, allowing you to create applications that are not only functional but also clean, efficient, and easy to maintain and extend.

Please follow and like us:
Pin Share