Handle User Input by Adding State to the Tic-Tac-Toe Grid with React useReducer

Kyle Shevlin
InstructorKyle Shevlin
Share this video with your friends

Social Share Links

Send Tweet
Published 4 years ago
Updated 3 years ago

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.

Antal Tony Tettinger
Antal Tony Tettinger
~ 2 years ago

Hello! Thanks for the nice course. Why don't use the object spread syntax instead of the clone function? I thought that is a general practice that I have seen in other courses. I found some other recommendations such as Object.Assign( {}, obj ), but in general I have seen the spread syntax being used in other courses.

Kyle Shevlin
Kyle Shevlininstructor
~ 2 years ago

Hello! Thanks for the nice course. Why don't use the object spread syntax instead of the clone function? I thought that is a general practice that I have seen in other courses. I found some other recommendations such as Object.Assign( {}, obj ), but in general I have seen the spread syntax being used in other courses.

The answer is simple. The spread operator will only do a shallow clone of an object's properties. If a property's value is another object, that reference hasn't changed. You now risk accidentally mutating an existing object. Using the clone function is a creates a deep clone of the entire object. This means that objects, arrays and other data structures have completely new references, and therefore, you won't mutate an existing object accidentally.

Antal Tony Tettinger
Antal Tony Tettinger
~ 2 years ago

Hello Kyle! OK, got it. So there is a new object created in the shallow copy case (spread operator), but if the property is another object it would be mutated. Very interesting, this has not been emphasised in many other courses so far and the spread operator is widely used. Thanks for the insight and the quick answer! Happy holidays!

Markdown supported.
Become a member to join the discussionEnroll Today