‫Let's now build
‫an advanced state management system
‫by combining the context API with a use reducer hook
‫which is a pretty common pattern in React development.
‫Now with all the knowledge that we already have
‫and with all the code that we already wrote here
‫this is actually not gonna be such a big deal.
‫So we already know about all the fundamental pieces
‫of this advanced state management system.
‫All we have to do is to now combine these three states here
‫into a reducer and then change our code here
‫a little bit to use that reducer function.
‫So basically what we're gonna do is to
‫refactor the entire code here in the city context to again
‫use a use reducer hook now instead of these use states.
‫So let's start by creating the reducer function
‫which gets the current state and the action.
‫Remember, and for now, I'll just leave it like this.
‫And then let's also create the initial state as always.
‫So again, we already know how all of this works.
‫So the cities start as an MT array.
‫Then we have isLoading, which starts at faults
‫and then the current city starts as an empty object.
‫So just what we have here.
‫So let's then get rid of these for now.
‫And then let's create the state and the dispatch function
‫out of the use reducer hook.
‫And so here we pass in as always the reducer
‫and the initial state.
‫And actually here we can immediately destructure the state
‫back into our cities isLoading and the current city.
‫Now, as soon as I'm gonna save this
‫then we will get some errors because,
‫well first of all here it's called reducer like this.
‫But worse than that is that all
‫of these set state functions here are no longer defined.
‫And so we are now going to replace those basically
‫with the dispatch function.
‫Now here inside our reducer function, remember
‫that we should put as many logic
‫as possible so that we have this central place
‫that handles all the business logic
‫and basically all the state transitions.
‫Now the problem in this case here is
‫that reducers need to be pure functions,
‫which means that we cannot do these API requests
‫that we do right here inside the reducer function.
‫So all we can do is to still make
‫these fetch requests out here.
‫So in separate functions.
‫And then after the data has already been received
‫then we can dispatch actions to the reducer.
‫So what this means is that when asynchronous data
‫and asynchronous code is involved,
‫then we don't get that nice benefit where we can simply
‫pass the dispatch function into the context value,
‫instead of all of these event handler functions like this.
‫But let's first implement a few actions here
‫and then we will come back to this topic in a minute,
‫because this is quite important to understand.
‫So let's again do a switch case here
‫and then of course checking for the action.type.
‫And in here let's create all different cases.
‫So when we use a reducer
‫in a bit larger application like this one
‫it's very important to follow a meaningful
‫naming conventions when it comes to the action types.
‫So it's usually a good idea to model these actions as events
‫and not as setters because this makes it easier
‫to see all the related state transitions.
‫For example, it shouldn't be set cities
‫but instead we can call this cities/loaded
‫or we can also do this, but it's a bit
‫of a naming convention to use a slash like this
‫at least in the Redux community
‫which is actually similar to what we are implementing here.
‫So I like to use this slash here
‫because it makes it really easy to understand
‫that this is related to the cities
‫and then that they have been loaded in this case.
‫So we will also have created and deleted.
‫And actually let's do that right now.
‫So we can say cities created
‫and also cities deleted.
‫And again, it's better to do it like this because then
‫it's really easy to see that in that event we
‫should both set the cities and probably also
‫set the isLoading state.
‫And so both of these things happen when the cities
‫are loaded, but if we just called it again, like set cities
‫then we might think that all we do in this event
‫is to set the cities.
‫And so this is why again,
‫we should model our event names more like this.
‫So really as events and not just setters.
‫So let's then actually do what I just said.
‫So here as always return the current state.
‫Then we set isLoading back to faults, and we set the cities
‫to action.payload.
‫All right, now here we are missing a default case again.
‫So let's throw a new error, like always
‫unknown action type.
‫Now here this says it needs a break statement
‫but we will take care of that in a minute.
‫Because if we just return here like we're doing here
‫then we need no break statements after each case.
‫But anyway, so here we will now be able to
‫use that dispatch function instead of these two.
‫However, what we're also going to need is
‫to set this loading state here
‫to true in the very beginning.
‫So let's also create an action for that and let's do it here
‫in a logical order in which the events actually happen.
‫So here, let's just call it loading.
‫So without the city's prefix.
‫So we want to take all the current state and then override.
‫IsLoading to true.
‫All right, and so now let's use these.
‫And here we can actually do that outside of the try-catch.
‫So this patch the event type of loading.
‫And then here, as soon as the data arrives
‫then we can dispatch the type
‫of cities loaded with the payload of data.
‫So this is what we were setting before
‫into the set city state.
‫And so now here we can also get rid of that.
‫But then the problem is that if there is some error
‫then the loading state will never get back to faults.
‫And so actually to make this a bit more complete
‫let's also create a new state here for the error.
‫So starting as an empty string
‫and then let's add a new case here
‫in the end called rejected.
‫And so notice how again,
‫I'm giving this year basically a name in the past.
‫So for something that already happened.
‫So we get the error here
‫in this function where the data is loaded
‫and after that we will dispatch the action.
‫And so then that rejection has already happened.
‫And so that's why then here we call it rejected.
‫So here we return the state.
‫Then we set isLoading also to faults
‫and the error to action.payload.
‫All right, so here, let's just replace that dispatch.
‫So where the type is rejected and the payload is going
‫to be done this string.
‫Now we're not really going to use this error state
‫that we just created in our application
‫because we're not doing much error handling there,
‫but this was just to make this a bit more complete
‫so that you can maybe use this
‫in the future for your own small applications.
‫So I think here we handled the entire loading of the cities.
‫And so let's move on here to the next one
‫and see what we need.
‫So we already took care of the loading states
‫but now here we need to set the current city.
‫So as soon as one individual city arrives from the API.
‫So here we need a new action or a new case for that.
‫So let's call this one city loaded.
‫So return.
‫Set to false, and then here it is called the current city
‫which is gonna be set to action.payload.
‫And so now this is all gonna be very similar.
‫So let me actually copy this here already
‫into all the functions.
‫And I will also get rid of all the finals everywhere.
‫So doing it all in one go.
‫Okay, and then this is the last one, I believe.
‫No. Okay, here we go.
‫And now here we just need to dispatch that action
‫that we just created.
‫So the type is city/loaded, and the payload is the data.
‫And then here, let's also just copy this entire dispatch.
‫And let's just change the string here.
‫So here, that's cities.
‫Okay, and now we can finally also create two actions
‫for those two.
‫So with this, we then actually finish this reducer.
‫So as soon as the city has already been created
‫we set the loading back to faults.
‫And then what do we do here with the current cities?
‫Well, that's right.
‫We add the new city also to our local state.
‫So exactly what we have right here.
‫And so remember how I told you earlier
‫that basically what we do here is to keep the remote state
‫in sync with our UI state.
‫So basically here we are adding the new city to our API
‫and then here we are also setting it to our local state.
‫So doing that manually, which again
‫we will solve in the next big project using React Query.
‫But anyway, for now here we will add that new city
‫to the current array of cities.
‫And so we spread the state.cities and add action.payload.
‫Alright? And then here, let's do something very similar.
‫So let's just actually copy this,
‫but now here instead of adding the new city,
‫we remove it using a filter.
‫So just like we did down here.
‫So let's just grab this filter
‫and of course we need to change it a bit.
‫So it's right here.
‫So here that is state.cities.
‫And here this ID is what we actually need to pass
‫in into this action.
‫So this will become action.payload.
‫And here there's some problem with that.
‫Okay. And so now let's then use those two.
‫So here we have cities created
‫or actually thinking about it,
‫this would just be called city.
‫So here we are talking about an individual city.
‫And so let's change that.
‫So all the cities have been loaded,
‫one city has been loaded, one has been created
‫and one has been deleted.
‫So that makes a bit more sense.
‫And yeah, so city created
‫and the payload is gonna be again the data,
‫which is the newly created city object.
‫And so here then let's again dispatch the rejected action
‫and then creating the city.
‫Okay, and then finally
‫let's also replace this one with the dispatch.
‫So here we have city deleted.
‫And then as I was mentioned here, what we need to pass
‫in is just the ID of the city that should be deleted.
‫Then here and here we replace this.
‫And as I save this, now you see
‫that all the errors that we had before have been resolved.
‫And so our application should now be back to working.
‫But before we try that out
‫let's discuss a bit what we just did here
‫and the two options that we have when it comes
‫to passing down the value into our context.
‫So as I said in the beginning
‫when we are working with asynchronous data and code
‫we have two options when it comes to the dispatch function.
‫So the first option is to pass
‫in all the state plus the dispatch function into the value.
‫And then we can use the dispatch function
‫inside the components to update state.
‫However, since we're dealing with asynchronous data
‫we cannot have all the logic inside the reducer.
‫And so in the case that we were passing
‫the dispatch function into the context
‫then we would have to have this function here
‫inside the component that dispatches the action.
‫And you can try that out for yourself, of course.
‫So you can remove this function from here
‫and not pass it into the context,
‫but instead pass in dispatch.
‫But then you would have to pass all this data fetching logic
‫into that component
‫which is not really what we want if we want
‫to keep the components nice and clean
‫and therefore we are using the second option
‫which is to not pass the dispatch function into the context
‫but instead to use it here inside
‫these event handler functions, which these are.
‫So these are event handler functions that are called
‫for example, here on the click of this delete button.
‫And so then this time we use the dispatch inside
‫of these functions.
‫And then it is these functions that we pass here
‫into the context, okay?
‫But if we were not dealing with asynchronous data
‫then it would be better to just pass the dispatch function
‫and then create the actions right inside the components.
‫But anyway, let's make sure that this actually works.
‫So let's come to some city here
‫and let's see.
‫And beautiful, that just works exactly as before.
‫And the same probably here
‫for loading and also for deleting.
‫Nice.
‫And so now with this structure in place, it is actually
‫quite easy to implement some more related state updates.
‫So that's one of the huge advantages
‫of having all the state updating logic in one central place.
‫For example, we can say that as soon as a city is created
‫it should also become the currently active city.
‫And so again, it's now very easy to do that.
‫So we can just say current city is equal to action.payload.
‫And so then if we somewhere here create a new one
‫then we should see that immediately
‫with this green outline right here.
‫And the same thing actually when we delete a city.
‫So there's no need to then keep it in the current city
‫and so we can set it back to the original state.
‫Great. And this is actually it.
‫So with this
‫we successfully converted the use states that we had here
‫to a reducer, and we even created a new state.
‫So we even created the error, which we're not really using
‫but well, let's just pass it into the context as well.
‫So that's right here.
‫Okay.
‫And now there's just one small thing that
‫we can improve here, which is really just a small thing.
‫So here when we get the city
‫we can check if the ID that is being passed
‫in is the same as the current city.
‫And so basically we can check if the city that we
‫want to load is already the current city.
‫And so then there's no need to call the API again.
‫So that's ID equal to current city.id, then just return.
‫Now here we will just have one problem
‫and let me actually just show it to you.
‫So this is something very important that we,
‫well it's not return,
‫we want to console.log this actually.
‫So let's clean this.
‫And here we are still undefined.
‫Let's try another one.
‫So what I wanted to show you here is that the second thing
‫so this ID right here is actually a number.
‫So that's why it has this purple color here.
‫While the ID that we have right here is a string.
‫And the reason for that is that we are actually
‫reading this ID somewhere in that component from the URL.
‫And so everything that's coming
‫from the URL will automatically be a string.
‫And so if you want to do any comparisons like this
‫then you always need to convert this to a number.
‫Now, right?
‫And so now if we then load two times the same city in a row
‫then it should be instantaneous.
‫So you notice that.
‫So in order to load the city
‫it of course needs to load from the API,
‫but if we then click the same again, it doesn't do that.
‫Okay.
‫And with this we really finished just make sure, again
‫to review everything that we did here
‫and how we are now handling
‫these related state updates together in these events.
‫So again, this combination of a use reducer
‫and the context API is a pretty common pattern in React.
‫And so therefore, it's important
‫that you now know that it exists
‫and that you hopefully also know
‫how to implement it yourself.
‫And by the end of this section, I will give you, of course
‫a challenge so that you can practice this as well.