HomeAbout Me

React Hook: Tic Tac Toe

By Daniel Nguyen
Published in React JS
June 07, 2025
2 min read
React Hook: Tic Tac Toe

🎮 Building Tic Tac Toe in React: Mastering useState, useEffect, and Game State Persistence

Want to level up your React skills with a hands-on project? Let’s build a classic Tic Tac Toe game—but with a twist! We’ll not only manage the game board and handle turns, but also:

  • Use functional updates with useState
  • Calculate derived state like the winner or next player
  • Persist the game with localStorage
  • Implement a time-travel-style history feature to revisit previous moves

Let’s dive in! 🏊‍♂️


🧠 Understanding useState Functional Updates

React’s useState lets you update state either directly or via a callback:

const [count, setCount] = useState(0)
// Direct update
setCount(count + 1)
// Functional update
setCount(prevCount => prevCount + 1)

🔑 When to Use Functional Updates?

Rule of Thumb: If your new state depends on the previous value, always use the function form. It ensures you’re working with the most up-to-date state—especially helpful when updates are batched or asynchronous.


🎯 Project Goal: A React Tic Tac Toe Game

Let’s outline the core concepts you’ll implement:

✅ Managed State

  • squares: A flat array of 9 elements representing the game board. Example:

    ['X', 'O', 'X', 'X', 'O', 'O', 'X', 'X', 'O']

🧮 Derived State

  • nextValue: Calculated based on how many “X”s and “O”s are on the board. (We’ve provided a calculateNextValue() utility for this!)
  • winner: Determined by checking win conditions. (calculateWinner() handles this logic.)

Note: Derived state is calculated on the fly—you don’t store it in useState.


✍️ Handling Square Clicks

You’ll update the squares array when a user clicks a square—but never mutate state directly!

❌ Don’t do:

squares[0] = 'X'

✅ Do this instead (using Array.prototype.with()):

const newSquares = squares.with(index, 'X')

This ensures immutability, which is essential for React to detect changes correctly.


💾 Persisting Game State with localStorage

Want players to resume the game even after closing the browser tab? Store game state in localStorage!

Step 1: Initialize from Local Storage

const [squares, setSquares] = useState(() => {
const stored = localStorage.getItem('tic-tac-toe:squares')
return stored ? JSON.parse(stored) : Array(9).fill(null)
})

Step 2: Sync Updates with useEffect

useEffect(() => {
localStorage.setItem('tic-tac-toe:squares', JSON.stringify(squares))
}, [squares])

Now your app is stateful and persistent!


⏪ Adding Time Travel: Game History

Kids hate losing? 😅 Let’s let them rewind the game and change their moves.

👣 How Game History Works

Instead of just tracking the current board state (squares), you track a history of all board states.

const [history, setHistory] = useState([Array(9).fill(null)])
const [currentStep, setCurrentStep] = useState(0)
const currentSquares = history[currentStep]

🕹️ Making a Move

When a player makes a move:

  1. Slice the history up to the current step (to discard future moves).
  2. Create a new board with the move.
  3. Push the new board onto the history.
  4. Update currentStep.
setHistory(prev => [...prev.slice(0, currentStep + 1), newSquares])
setCurrentStep(prev => prev + 1)

🔄 Navigating Moves

Add UI buttons to jump to any past move:

<button onClick={() => setCurrentStep(index)}>
Go to move #{index}
</button>

🧼 A Word on Testing & localStorage

🧪 Be cautious! If you’re writing automated tests or switching between development and test modes, your localStorage values can conflict. Clear the storage between sessions or use unique keys to isolate environments.


📚 Summary

Here’s what you’ll master in this exercise:

FeatureConcept
Game board (squares)Managed state
Player turn / winnerDerived state
Local game persistenceuseEffect + localStorage
Undoable move historyFunctional setState and immutability
Performance & correctnessuseState callback form

This isn’t just about building a simple game—it’s a deep dive into how React state really works, and how to structure real-world, user-friendly features.


🔗 Resources

  • React Docs: useState
  • MDN: localStorage
  • Blog: useState Lazy Initialization & Functional Updates

Happy coding! 🧠💡 Let me know if you’d like a working code template for this.


Tags

#ReactHooks

Share

Previous Article
React Hook: Unique IDs

Table Of Contents

1
🧠 Understanding useState Functional Updates
2
🎯 Project Goal: A React Tic Tac Toe Game
3
✍️ Handling Square Clicks
4
💾 Persisting Game State with localStorage
5
⏪ Adding Time Travel: Game History
6
🧼 A Word on Testing & localStorage
7
📚 Summary
8
🔗 Resources

Related Posts

React Hook: Unique IDs
June 06, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Quick Links

About Me

Legal Stuff

Social Media