In order to update our UI when a user interacts with the buttons, we need to store and update the tic-tac-toe grid
data somehow. We're going to use the React.useReducer
hook to accomplish this.
With this hook, we are able to store some data (including our grid) and define actions
that update that data. We'll work on adding a CLICK
case to our reducer
that will update the grid
with either an X
or an O
according to whose turn
it is.
Kyle Shevlin: [0:00] Now, we're going to make the grid of our game stateful and update when we click on cells and Tic-Tac-Toe. To do this, we'll use React's useReducer hook. We'll start by creating the initialState of our component. This will be an object with a key of grid, and we'll just call newTicTacToeGrid() here.
[0:20] We'll also create a shell of a reducer function. Reducers always accept the currentState and an Action and return the nextState. Right now, we'll just return that state. We'll handle the clickAction momentarily.
[0:34] Now, we will set up a game to use this.state instead, so we'll array the structureState and dispatch from a React.useReducer hook, where we pass in the reducer and the initialState. We also need to destructure grid from our state -- add a little whitespace. Our board is still exactly the same, which is exactly what we wanted.
[1:02] Now inside of our reducer, we can start to handle the click action. We're going to start by taking dispatch here. What we're going to do is we're going to dispatch an action, that type will be of type CLICK and it's going to have a payload of an object of the x and y coordinates.
[1:22] From here, we now need to add a case in our reducer that will handle the CLICK action.type. We're going to switch this to a switch statement. That switch statement will accept the action.type as the expression that it will switch on.
[1:38] By default, we want to return state in case there's some action that we don't account for and we'll start with the CLICK case. I'll have a case of CLICK and what we'll do is we'll start by destructuring x and y of the action.payload that we receive.
[1:58] We know that eventually what we want to do is return the nextState. How are we going to do this? What I like to do, sometimes, especially with games is I like to make a clone of the currentState, and make mutations on that, so that I can return an immutable update without changing the state that was passed into the reducer.
[2:21] To do this, I'm going to come up here, I'm going to make a quick helper function for myself. We'll call this clone. A simple way to clone values in JavaScript, to do it deeply on arrays and objects is to call JSON.parse(JSON.stringify) version of your value. This is a very simple way to make deep clones.
[2:44] What I like to do is, I will make a nextState as a clone of our currentState. Now, I can make mutations on nextState without any fear of ruining the previous state.
[2:58] Now before we return state, we do want to have a few guards in place. If it turns out that state.grid y x...Remember, our rows are the first value in our grid, not our columns. You want to get used to putting your y before your x value.
[3:16] If that exists, that means it's an x or an already. We can return the state as is. I don't want to accidentally click on a already-clicked-on square. This is a very simple way of handling that. By returning the original state object an update won't occur, because this state is the same reference. That's how reducers know not to produce an update.
[3:40] If we get past here, we know that our cell has changed. We need to somehow indicate that nextState.grid y x should equal a certain value. What should it equal? We need to keep track of whose turn it is in our game. We'll come back up here to initialState, and we'll now add a value here.
[4:02] Every game of Tic-Tac-Toe starts with an x. This means here, what we can do is, we can destructure some values off of here. We can destructure grid and turn, off the currentState. We can get rid of that. We know that we'll want to set this eventually to turn.
[4:24] How do we then update this turn on the next one? We could use a simple ternary. We could also use an enum, which is something that I really enjoy doing. Even if it seems like a little overkill here. We'll make an object called NEXT_TURN. What this object will simply be is, the key of will return the value of X, and the key of X will return the value of .
[4:47] Now, we don't really need any logic. We can simply say nextState.turn = NEXT_TURN [turn] . We need to do these things here. Sorry, I have that in the wrong order. Luckily, I haven't saved and run our code yet. Now, I've saved this. We can click values, and we can see that they show up in our grid.