diff --git a/exercises/01.use-reducer/06.problem.tic-tac-toe/README.mdx b/exercises/01.use-reducer/06.problem.tic-tac-toe/README.mdx index d8066aa2..6d21bd5f 100644 --- a/exercises/01.use-reducer/06.problem.tic-tac-toe/README.mdx +++ b/exercises/01.use-reducer/06.problem.tic-tac-toe/README.mdx @@ -4,4 +4,43 @@ We'll be refactoring the `useState` out of our tic-tac-toe game to use `useReducer` instead. +Your reducer should enable the following actions: + +```tsx +type GameAction = + | { type: 'SELECT_SQUARE'; index: number } + | { type: 'SELECT_STEP'; step: number } + | { type: 'RESTART' } + +function gameReducer(state: GameState, action: GameAction) { + // your code... +} +``` + +Note that to do the lazy state initialization we need to provide three arguments +to `useReducer`. Here's an example for a count reducer: + +```tsx +// ... +function getInitialState(initialCount: number) { + return { count: initialCount } +} + +function Counter() { + const [count, dispatch] = useReducer( + countReducer, + props.initialCount, + getInitialState, + ) + // ... +} +``` + +Notice that the `getInitialState` function is called only once, when the +component is first rendered and it's called with the `initialCount` prop which +is passed to the `useReducer` hook as the second argument. + +If you don't need an argument to your initial state callback, you can just pass +`null`. + Good luck! diff --git a/exercises/01.use-reducer/06.problem.tic-tac-toe/index.tsx b/exercises/01.use-reducer/06.problem.tic-tac-toe/index.tsx index 5a43e859..c5a19cfd 100644 --- a/exercises/01.use-reducer/06.problem.tic-tac-toe/index.tsx +++ b/exercises/01.use-reducer/06.problem.tic-tac-toe/index.tsx @@ -48,13 +48,25 @@ function Board({ ) } -const defaultState = { +const defaultState: GameState = { history: [Array(9).fill(null)], currentStep: 0, } const localStorageKey = 'tic-tac-toe' + +// 🦺 Create a GameAction type here which supports all three types of state changes +// that can happen for our reducer: SELECT_SQUARE, RESTART, and SELECT_STEP. + +// 🐨 Create a gameStateReducer function which accepts the GameState and GameAction +// and handle all three types of state changes. +// 💰 you can borrow lots of the logic from the component below in your implementation + +// 🐨 Create a getInitialGameState function here which returns the initial game +// state (move this from the useState callback below) + function App() { + // 🐨 change this to use useReducer with the gameStateReducer and the getInitialGameState function const [state, setState] = useState(() => { let localStorageValue try { @@ -79,6 +91,8 @@ function App() { }, [state]) function selectSquare(index: number) { + // 🐨 move this logic to the reducer + // then call the dispatch function with the proper type if (winner || currentSquares[index]) return setState(previousState => { @@ -94,6 +108,7 @@ function App() { } function restart() { + // 🐨 update this to use the dispatch function with the proper type setState(defaultState) } @@ -107,6 +122,7 @@ function App() { return (