If you already have a subscription, you can sign in.
Enjoy free content straight from your inbox 💌
00:00
If you are looking to level up your state management team while using APIs that come built in with React, a great way to do that is to graduate from U State to the use reducer hook. In this lesson, we will cover all that you need to know about use Reducer and how it can give you a nice control pattern for managing state changes. So let's go. Let's create a simple React application to demonstrate the point at which you should think about swapping over from use state to use reducer. We have two simple state variables. One to manage the count of the total changes that have taken place
00:32
and one to manage a simple string value within our ui. We display the change count as well as the current value within simple divs. And finally we have an input element which is wired to the value. And every single time the value changes, we make sure that we update the value as well as increment the current change count. And of course, if we run this application, you can see that it works as expected, the user can input any value and the change count increments and the value gets updated. Now, even though this definitely works,
01:04
there are some concerns with the maintainability of this approach. It doesn't provide a nice separation of the mutations and the consolidated combined state. This logic is embedded into our application and it's hard to understand it without having to read the entire application source code. The beauty of the use reducer hook is that it allows us to narrow down the API footprint and consolidate the logic into a single reducer function code to the use reducer hook is the Redux mental model where we write a reducer to do a reduction. Let's create a neat mental map to contrast U State
01:39
and used reducer. Let's first look at how this U State application is functioning. When the user carries out an event like changing the input, we hand it off to a function and then that function is responsible for setting the individual states to their new values. And all of this logic is embedded into our app code. Now let's contrast this To use reducer on a user event, we dispatch an action off to a function and this function can be completely independent of React. And this reducer function is responsible
02:12
for taking a previous state value along with an action object and then return the updated state value. Let's look at this in action by recreating our application with use reducer. Use reducer is exported from React and we can import it just like we import use state. There are two key pieces of objects that a reducer works with and that is a state object and an action object. So let's define what these objects are going to look like For our specific use case with TypeScript types, the state is simply an object that has a change count of type number
02:46
and a value of type string. And now the action is going to be a set of different objects, one for each different kind of action, which we can put into a union to allow for future expansion. Right now We only have one member in this union, which is an object of type update, and this expects to be passed in a value of type string as well. With these types defined, we can create a simple reducer function. It takes a previous state and action and is designed to return an updated state within the reducer body.
03:18
We add a switch statement where we decide what the updated state is going to look like based on the provided action type. Right now we have only one action of type update, and for that particular case we will return an updated state object that increments the change count by reading the state change count and adding one and then provides the value based on whatever is provided in action value. And this update body is pretty much all that we need to carry out the same mutation that was happening before, but now we have a nice controlled pattern
03:50
for doing any future modifications as well. Now of course we are going to need an initial value for the state and it's good to create a separate variable for that so we don't have to create it again and again. And with this reducer function and the initial state, we are ready to utilize the use reducer hook. We invoke use reducer within our application component passing in the reducer as well as the initial state value. And this hook returns an array containing two objects. The first is the state value, which might be the initial state
04:21
or if it has been changed, the updated state as well as a function called dispatch, which can be used to dispatch actions. So whenever we want to display the change count or the value, we can just pick them off of this state object. And then finally, when we want to make a mutation to the state, we need to dispatch an action to the reducer, which of course we can do with this dispatch function. And here, whenever the user edits the value, we will dispatch the action of type update with the updated value from the event.
04:54
This action gets passed to the reducer, which of course returns the updated state, which of course then react uses to re-render the component. So if we jump to the ui, you can see that it works exactly how you would expect. It has the same behavior as before, but obviously if we look at the code, we now have a nice control of the modifications that are taking place on all of the state. Perhaps the biggest advantage of using the reducer is that allows you to test and understand the reduction logic independent of React. To demonstrate that, let's create a simple test within an
05:27
APTA test tsx and then we import the reducer as well as the initial state. And for our test body, we will follow the triple A standard of testing, which is range, and we don't need much arrangement for it. We just initialize a variable using initial state. And then we act on this variable by using our reducer. Providing in an update action will be update the value to edge to get an updated state as the after variable. And finally, we assert that the reducer does indeed Do what it is supposed to.
05:58
That is increment the change count, which should now be one, and then set the value which should now be edge. And of course, if you run this test, it is going to pass because as we saw, our application is working perfectly fine. Note that we have managed to test our reducer without importing react at all. Since the reducer is independent of React, we can actually even organize it in its own module. It's actually quite easy to do. We can select the types for the state and the action and then select the reducer as well as the initial state variables
06:30
and then choose to move them into a new file. Once we've done this within VS code, or you can use our IDE of choice, we get this new module containing all of these things and having our application broken into modules with clear responsibilities further enhances the maintainability and the scalability of our application. To get a bit more hands-on experience with using a reducer, let's expand our example by adding an additional action. Because we already have the infrastructure in place, this is actually going to be quite easy. We can define the new type for the action,
07:03
and we are going to add this action of type reset. The objective of this action is going to be reset the stage to its initial value. And this action also demonstrates an additional point that if your action doesn't need a payload, for example, we needed the payload of a value with the update action, but since reset doesn't need a payload, it can be pretty darn simple thanks to our existing infrastructure. Adding a new reduction is pretty simple as well. We simply add a new case for the reset action, and within that our objective is to return the updated state, which in our case is going to be the initial state once more.
07:35
And that's it for the application logic. The final step is to add some user event to trigger this particular action. Within our app component. We add a simple button and on click we will dispatch the action of type reset. And that's all it took to add a new feature. The existing feature still works, we can add a value, we can edit it, and now we have this reset button, which can take the user back to an initial state along the user to continue their journey along a different path as if they were starting from scratch. One thing to note is that it is actually quite easy
08:09
to forget handling all of the cases within the reducer, and then it'll fall through to the default case where we throw that error unknown action. Fortunately, it is quite easy to catch this issue and fix it at compile time thanks to TypeScript. To demonstrate what the experience would be like without TypeScript, let's just choose to intentionally forget to handle the reset case within our reducer. You can see that no error is pointed out by our IDE, and if we jump to our application, it does not error out immediately and only once we click the reset button,
08:42
the application crashes with the error unknown action. There is a feature within TypeScript called the never type, which we can use to make sure that all of the action types are handled Within this switch Statement. We are creating a variable of type never and trying to assign it the action object. And the reason why type three is complaining right now is because action can still potentially be the reset object. And if you hover over the error, you can see this within the error message only once all of the cases for the action type have been handled will this
09:16
particular statement of trying to assign action to a never would pass. And if we add that case for research, the error goes away because now TypeScript knows that the default block is never going to execute and therefore it is fine to assign action to a variable of type never. And with the reset functionality restored, of course our application is going to work perfectly fine. The user can provide a value and reset to their hearts content. I'll wrap things up there. As always, thank you for joining me and I will see you in the next one.