Fullstack CourseLearn by building
Back to roadmap

Week 1 — HTML, CSS & React UI foundations

Learn HTML document structure and CSS layout (box model, positioning, Flexbox/Grid), then build React foundations — then ship a Tic-Tac-Toe mini-project.

End-of-week mini-project · practice only

Tic-Tac-Toe

Build a playable Tic-Tac-Toe game with React components, immutable state, time-travel history, and a small React Router shell.

Definition

A single-page React game that applies every Week 1 foundation — semantic HTML/CSS layout, components and props, conditional rendering, keyed lists, purity, events, immutable state, and SPA routing — with no Redux, Query, or Next.

In simpler words

Two players take turns on a 3×3 board. You own all the UI and game state in React, add a history you can jump back to, and wrap it in a couple of routes. No Nest yet.

Concepts covered this week

HTML document structure
A semantic shell: a <main> game region, a status <output>, and a <nav> for the menu/history routes.
CSS box model & normal flow
Padding/border/margin size the squares and gaps so the 3×3 grid stays square.
CSS positioning & stacking
A win/draw banner overlays the board with absolute positioning and z-index.
Flexbox & Grid alignment
CSS Grid lays out the board; Flexbox centers the status bar and controls.
What is React & your first component
Start from one <Game> component, then split into Square/Board/StatusBar.
Importing & exporting components
Each component lives in its own file and is imported where it is used.
Writing JSX & JavaScript in JSX
Render nine squares and interpolate the status string with curly braces.
Passing props
Board hands each Square its value and an onClick handler.
Conditional rendering
Show “X’s turn”, “O wins”, or “Draw” from derived game state.
Rendering lists with keys
The move-history list maps moves to buttons with stable keys.
Keeping components pure
Square renders only from props — no mutation or side effects during render.
Responding to events
Handlers are passed down and run on click, never called during render.
SPA routing: React Router & guards
Routes for a menu, /game, and /game/:id history; a guard blocks history when no game was played.
State: memory, render/commit, snapshot & queueing
useState holds the squares and current player; each click re-renders from a fresh snapshot.
Updating objects & arrays in state
Copy the board array (slice) and push to history without mutating the previous turn.

Deliverables

  • Square / Board / Game / StatusBar components in their own files with props down and events up
  • useState for the squares array and current player; winner/draw/next-player derived from state (no DOM reads)
  • An immutable move history with “jump to move” time-travel rendered from a keyed list
  • A small React Router shell: menu → /game → /game/:id history, with a guard on the history route
  • A responsive board built with CSS Grid/Flexbox and an overlay result banner

Build guide

  1. Build the static boardLay out nine Squares with CSS Grid and semantic HTML before adding any state.
  2. Lift game stateMove the squares array and current player into <Game> with useState; pass value + onClick down to each Square.
  3. Derive the statusCompute winner/draw/next-player from the board with a pure helper and show it via conditional rendering.
  4. Update immutablyOn click, copy the array with slice, set the cell, and ignore clicks on filled cells or after a win.
  5. Add move historyStore each board snapshot in a history array and render a keyed “jump to move” list that time-travels state.
  6. Wrap it in routesAdd React Router: a menu route, /game, and /game/:id history; guard the history route so it redirects when no game exists.
  7. Polish the UIStyle the grid, add an overlay banner for win/draw, and make the empty board look different from a finished game.

Acceptance checklist

- [ ] Board renders 9 squares from a state array (not nine separate states)
- [ ] Clicking a square updates immutable state (no board[i] = 'X' mutation)
- [ ] Winner / draw / next player shown with conditional rendering
- [ ] Handlers are passed, not called during render
- [ ] Move history uses stable keys and can time-travel
- [ ] React Router shell works; the history route is guarded
- [ ] Empty board ≠ finished game (distinct UI)

Stretch goals

  • Add an unbeatable AI opponent (minimax) as the O player.
  • Persist the current game to localStorage and restore it on reload.
  • Add a scoreboard route that tallies wins across games.

Tips

  • Follow react.dev’s Tic-Tac-Toe tutorial first, then rebuild from memory without copy-paste.
  • HTML/CSS foundations still apply: semantic structure, box model, and Grid for the board.
  • Keep it local-only — no Nest or Redux yet; state lives in React.
  • Add the Router shell last so the game works before navigation does.