
Lesson Description
The "Context and State Machine Exercise" Lesson is part of the full, State Management at Scale in React & Next.js course featured in this preview video. Here's what you'd learn in this lesson:
David instructs students to refactor a complex multi-step form in ExerciseReducer.page.tsx using useReducer to manage state more efficiently. He also walks through a solution, including creating a context, provider, and dispatching actions to streamline state management and improve user experience by centralizing data handling.
Transcript from the "Context and State Machine Exercise" Lesson
[00:00:00]
>> David Khourshid: So let's get to the exercise. We're going to be working in exercise-reducer > page.tsx. And you can see that there's a lot of things going on here. This is basically just a beefed up version of the demo that we just saw. There's a lot of confusion over here, a lot of state happening over here.
[00:00:23]
We have our typical submit with IsSubmitting, IsError, setSearchParams, etc. And so things get pretty complicated over here. We also have two components, or two and a half if you count this random is error over here. And we're passing in issubmitting on submit where it's just giving back the information.
[00:00:47]
And we're also passing the flight options and things like that to the search results, and we're all basing that on some random boolean value. So we have a pretty inefficient way of managing states and sharing that states between multiple components. And again, in your applications, you might think that this is fine.
[00:01:10]
You might say, hey, everything works. But even looking at this simple example, it is a bit confusing to see where all the states come together and also how updates are done. There's a lot of prop communication happening. So things are just really confusing. And furthermore, I don't think it even works fully.
[00:01:31]
So if I go to exercise on the actual UI, I type in the destination, whatever, Timbuktu, set my dates and go search for flights, sure, this will work. It will show flights, I'm able to select the flights. But once I go back to search, that information disappears. And as a user, I'm pretty frustrated about that.
[00:01:57]
But if we have some sort of centralized state management, then that information doesn't have to disappear. We could actually control what that data is so that we could show it on the page when we get to it again. So we're going to be doing a bunch of things in here.
[00:02:13]
We're going to be converting this to using useReducer and we're going to be sharing that reducer with those two components.
>> David Khourshid: All right, so we're going to be refactoring this multi-step form into using useReducer. Now instead of just showing you, okay, let's delete all of this code, and then I'm going to show you the answer and we're going to replace that code all at once.
[00:02:46]
I really want to emphasize the technique that I've been showing you before, where you want to be working on the refactor side by side with the actual code. And this is to make sure that all of the behavior and all of the logic is exactly the same when you're dealing with refactors, without doing anything destructive.
[00:03:06]
And this is especially important if you don't have these end to end, or unit tests, or just enough coverage in your application. So again, we're going to be doing this side by side. And the first step, like we learned about in previous lessons, is modeling what we want our state to look like.
[00:03:25]
So that's why we're going to start with the booking states. And just based on all of our Boolean variables, we have a status of, we start with idle where we haven't searched yet. And then we're searching and then, eventually, we get results. We could also have an error.
[00:03:49]
There's a state for that. We also have flight options which we want to maintain in this state as well. And we have our search parameters that we want to show. So we'll fill that out. So we have our destination, departure, arrival passengers is one way. And again, this stuff comes from what we already have on the page there.
[00:04:12]
We just want to centralize it into one state that can be represented in the reducer. And let's get our initial state too. So const initial, let's call it BookingState, which is going to be a BookingState. Status, we're going to start with the idle state. And there's no flightOptions, so we'll put that as null.
[00:04:36]
And also the search params are also going to be null. So we will just have that as null. And then the second thing we have to think about is the actions, so basically, what can happen in each state. So we could have, for example, let's do union over here.
[00:05:01]
We could first submit, and this payload is going to be everything in those search params. So this is submitting for the search parameters, so that we could start the search. And eventually, hopefully, we'll get results. And so the results are going to include flight options. And we could also go back, so type back.
[00:05:26]
And as always, there is a possibility for an error, we're going to have type error, too. And now it's time to actually build out the reducer. So the reducer, we have the state, and it's going to return the BookingState. And then we're going to be switching on our actions for now.
[00:05:48]
So first of all, when we submit, we want to transition to the searching state. So this is where we show the loading data. And we're also going to be saving the payload of that action, which is the destination, departure, arrival, etc. Now we are, hopefully, going to eventually get results.
[00:06:12]
And so that's going to allow us to change the states too, and change the flight options to whatever we get from those results. Now, right now, we're not using type states, but this is something that you could refactor on top of what you have once you have this working.
[00:06:32]
Case back, we're going to go back to that in a minute. But let's just handle if we have an error, then we have an error status. And by default, if we have some sort of unrecognized action, we return the state. Now case back, this is interesting because it really depends on where you're coming from, which we're actually going to see in the next exercise.
[00:06:58]
But only if the status is results, do we actually want to go back to the idle state. Otherwise, we return the state. So if you have a back button that is just present on every single screen, but you don't want it to do the exact same thing or go back to the same page on every screen, then you do have to basically tell it where to go based on the state.status.
[00:07:29]
And so that's why, especially in my XState courses, we talk about going based on the states first, rather than on the actual events or actions. But in this case, it's simple enough. We could just check, is the state results? Yes, let's go to idle from the result state when the user presses the back button.
[00:07:52]
Okay, so now it's time to actually use these. And we're going to go back to the app over here, and this is the main one. We're going to go over here. And then we're going to use that reducer and we have state and dispatch. And so now just like I was talking about with the Strangler Fig pattern, you're going to be doing things side by side.
[00:08:20]
So for example, when you handle submit, you want to dispatch. And you're going to have type submit, and the formData as payload. You're going to keep everything else the same, at least for now, because again, you're working side by side with the existing code. So we're going to, for example, over here, once we have those mockFlights as a result, we're going to dispatch that.
[00:08:44]
And then same thing here, we'll dispatch an error, when there's an error. And we don't actually need this finally block. And from here, we could console.log the state. So, console.log the state. And this just allows us to see, are we on the right track? Is the state as we expect it to be?
[00:09:11]
But we do need to share all of that contents too. So we know that, just to make this easier, we're going to have to make a different component. So I think I called that, what did I call that? Booking content, so we are going to create that context.
[00:09:32]
So const BookingContext, we create the context which includes the state and a way to dispatch that action. And then we are going to use that context in our components, but we need to provide it first. So we will make a function BookingProvider with the children, have the state and the dispatch in there.
[00:09:55]
And then, pass that in so that the children can use it. And so now that way, inside the Page, we could wrap everything inside that BookingProvider. So BookingProvider over here, and then we could start to use all of this stuff. So even without changing anything here just yet, again, we're making minimal changes.
[00:10:20]
We could grab, oops, not over there. But we could grab state, dispatch, and we could use that existing BookingContext in there. And so instead of onSubmit over here, we could just dispatch, submit, pass it in the payload. And then, yeah, forgot that one. And just do things side by side.
[00:10:49]
So over here, we're keeping track of the internal state. This, hopefully, is something you recognize from earlier exercises where it's like, hey, first of all, all of this data is related. So we could, potentially, put it in a single object just to clean things up a bit. But also, we could just read that from the form data because this is just a simple form.
[00:11:12]
And so that's an exercise left to you, but this is something that you could definitely do. So now when we try this, even though we're not quite finished yet, if I try this out, I could see that once we search flights, okay, we're logging that the status is searching.
[00:11:33]
And now we see that the status is results. And we also have our flight options over here, so we know that our reducer logic is working. And so from there, we could go and start just making sure that everything works side by side, and then start to remove the old use dates and the old logic and replace it with the centralized reducer pattern.
[00:12:00]
Yes?
>> Male Student 1: Why wasn't the selected flight included in the booking state?
>> David Khourshid: You know what, that's also an exercise left to the reader. You could definitely include the selected flight in the booking state. In this case, it was just omitted. But if the selected flight is relevant, which in a realistic app, it is.
[00:12:26]
If it is relevant to the overall global states that the useReducer represents, then it should be in there, and it doesn't need to be in the component itself. So that's a very good point. We could definitely put selectedFlight over here. But again, remembering a previous pattern, we should use an id, not the actual selected flight, because we don't want to duplicate that state.
[00:12:57]
So yeah, you could definitely do that. And then we could add an action to select the flights, and pass it in a string. And then it's asking for other stuff over here, yeah.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops