Build a Form with useReducer in React

Ryan Harris
InstructorRyan Harris
Share this video with your friends

Social Share Links

Send Tweet

Normally, you would use useState to store values in your function components. However, when your state value is a complex data object or is comprised of a number of related values, you may want to use useReducer instead. This hook allows you to avoid having numerous state hooks and, instead, consolidate your values.

Like useState, the array returned by the hook contains two values: your current state and a dispatch function which allows you to update the state. When instantiating the hook, you'll need to pass it two arguments, a reducer function and your initial state. Similar to patterns used in libraries like Redux, when you dispatch an action the reducer function body will determine how to update the internal state.

This is the function signature of the useReducer hook:

const [state, dispatch] = useReducer(reducer, initialState)

Ryan Harris: [0:00] In this example, we have a form with three different inputs. Instead of using three useState hooks, let's use useReducer, since these values are related to each other.

[0:10] Let's start by instantiating the hook inside of our AppointmentForm component, const [state, dispatch] = useReducer(reducer, blankForm). On the left-hand side, we have an array with two values -- state, which represents the internal state of the reducer, and dispatch, a function that allows us to manipulate that state.

[0:37] On the right side, we have reducer, which is a function that determines how we update the state, and blankForm, which is the initial state of the reducer.

[0:46] Let's define this blankForm value and say const blankForm = an object. Inside, we'll have a field with the initial value for each of the inputs in our form, so name will be an empty string, date will be an empty string, and time will be an empty string.

[1:08] Let's come down here and define a function called reducer(), and it'll be empty for now. We'll come back to this in a second.

[1:15] Let's go down to our markup and wire up our inputs to the state we just created in our reducer. We'll come down here to our Name input first, and instead of an empty string, the initial value will be state.name. Then we come down to our Date input here, same thing. Instead of an empty string, it's going to be state.date. Lastly, the Time input, instead of an empty string needs to be state.time.

[1:42] Let's go back up to the top and look at our reducer hook here. We've already defined the initial values here as the object blankForm, but we have yet to define the reducer function. The reducer function contains logic that updates our state based on the type of action that we dispatch.

[2:00] Let's start defining this function which takes two arguments, the current state and the action that was dispatched. Inside reducer, let's add a switch statement, this will look at the action.type. To start, let's create three case statements, one for each of the input values we're keeping track of.

[2:21] We'll start with case "setName", and this will return an object that spreads in our current state and updates the name field to be that of action.value. Then we'll add a case "setDate", which will return an object that spreads in our current state and updates the date value to be action.value. Lastly, we'll say, case "setTime". This also returns an object that spreads in our current state and updates the time to be that of action.value.

[2:59] In order for these updates to take place, we need to dispatch actions from our UI. Again, let's go back down to the markup. Here in our Name input, we'll add an onChange prop. This is a function that takes an event. Then what we're going to do is we're going to call our dispatch function from above. We're going to pass it an object with the type of "setName", and then the value of event.target.value.

[3:31] We can do the same thing for our other inputs by copy and pasting this onChange. For our Date, we paste this here, and say setDate instead. Then down here for our Time input, we'll do the same thing, but also change this to be setTime.

[3:50] If we come over the form, you can see that our values are being updated properly, but our Reset button doesn't work. How can we fix this?

[3:58] First, let's go up to our reducer and add a new case. Here, we'll add another case, case "resetForm". Because we want the form to go back to its initial values, we can just return the blankForm object.

[4:13] Lastly, we need to come down to our markup again to wire up the button. If we come down to our Reset button here, we can add another prop called onClick and this takes a function. What we're going to do here is we're going to dispatch an action again, and this time the type will be "resetForm," but we don't need to pass a value because our reducer is not looking for one. Let's save this.

[4:39] If we come over to our form and say, Ryan, we'll set the date to tomorrow. Now, if we click the Reset button, you can see that the form resets.

[4:48] In summary, useReducer allows us to store complex data objects or related data values without needing multiple useState hooks.