DEV Community

Sreeharsha
Sreeharsha

Posted on • Edited on

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.

Image description

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
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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]);
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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).

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);
Enter fullscreen mode Exit fullscreen mode

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} />
      )}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

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.

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more