Refactoring Our UI Into Hooks - Part II

undraw svg category

Welcome to Part 24 of Up and Running with FastAPI. If you missed part 23, you can find it here .

This series is focused on building a full-stack application with the FastAPI framework. The app allows users to post requests to have their residence cleaned, and other users can select a cleaning project for a given hourly rate.

Up And Running With FastAPI

Previously, we refactored the majority of our auth and UI related logic into composable hooks. Doing so made our components leaner and gave us a framework by which to structure future improvements to our frontend.

We’ll use that same approach in this post, but we’ll shift the focus towards cleaning jobs. Keep in mind that we’re gearing up for handling evaluations and aggregate statistics. The bulk of the work we do here should make things easier in that regard.

Updating the cleanings redux slice

Currently, the process by which we manage and view individual cleaning jobs is clunky. We’re going to clean up our redux/cleanings.js file a bit and then follow the pattern we adopted in our last post, moving logic out of components and into custom hooks.

Before we begin, tweak the redux/initialState.js file slightly.

redux/initialState.js

Just a one line change that precludes what’s to come in our redux refactor.

Ok, now let’s pop open that redux/cleanings.js file and make a few changes.

redux/cleanings.js

All of the updates to our action creator functions are simply cosmetic. Mixing snake case and camel case is usually not a good idea as it makes for an inconsistent api. Now, tools like VS code will handle autocompletion for us. Even so, it’s good practice to stick with standard JavaScript snake case until we need to actually pass camel case parameters to our FastAPI backend. So we’ve converted parameters like cleaning_id to cleaningId.

The other changes we’ve made are in the cleaningsReducer. We are also standardizing how we handle receiving cleaning resources from our FastAPI server, which is a much needed change. We’ll be keeping all jobs indexed by id inside the data key, creating a simple lookup table when we need to access a particular resource. On top of that, we’re simply storing the active id of any successfully fetched cleaning job and caching it in activeCleaningId. Any clunky update logic has been replace as well. All of these changes will break things as they currently stand, but that’s ok. We’ll fix them in a minute.

Oh yeah, we also switched to absolute imports here as well. All is well at the moment.

Next up is creating a new custom hook that we’ll use to manage any individual cleaning resource.

The useSingleCleaningJob Hook

Go ahead and create a new directory inside our hooks folder called cleanings. Add a new file to it called useSingleCleaningJob.js.

While we’re at it, let’s also create a new file in the utils directory called cleanings.js. Over the next couple steps we’ll be putting a few helper methods to good use. Now is as good a time as ever to start adding code there.

First things first, add the following code to the useSingleCleaningJob.js file:

useSingleCleaningJob.js

A few things to take note of here.

At the top of the file we’re importing some standard redux stuff and a yet-to-be-created function called userIsOwnerOfCleaningJob. Its job is self-explanatory. We should be able to pass it a user object and a cleaning object, and we should get back a boolean indicating whether or not that user owns the cleaning resource in question. We’re also importing the useAuthenticatedUser hook that we defined in our previous post. Once again, centralizing our app’s auth logic in this hook is proving fruitful.

The useSingleCleaningJob function takes in a single parameter, the id of the cleaning job. We then use that id in two places.

First, we pull the cleaning job from the cleanings slice of our redux state tree. We also grab the isLoading and isUpdating flags, along with any errors. We then pass both the cleaning job and the user returned by our useAuthenticatedUser hook into the userIsOwnerOfCleaningJob utility function to determin the value of the userIsOwner boolean.

Finally, we run a useEffect hook that checks if the cleaningId is valid and that we don’t already have a cleaningJob in state. If both these conditions are met, we dispatch the fetchCleaningJobById action creator and request the cleaning resource from our FastAPI backend.

At the end, we return all relevant data that consumers of this hook might need.

All that’s left is to implement the userIsOwnerOfCleaningJob function.

utils/cleanings.js

Three situations are being accounted for here.

Now onto the tricky part. We’ll need to update any component that depends on accessing a single cleaning job from redux.

Before we do anything else, make a small change to the CleaningJobsPage.js component.

CleaningJobsPage.js

Here we’re specifying the route param as cleaningId instead of cleaning_id. Again, we’re simply staying consistent with our variable casing.

Next, open up the CleaningJobView component and update it like so:

CleaningJobView.js

Compared to what this component looked like before, this is a signficant upgrade. To be fair, we’ve left out anything having to do with offers, so we’ve added a number of null and empty array values to children of this component. Once we start handling fetching offers for a single cleaning job, we’ll update these values.

We’re no longer using any redux related code directly in the component. Everything we need comes directly from our useSingleCleaningJob hook and is passed to the components that need them. Both the CleaningJobCard and CleaningJobEditForm use the same cleaning job, so we pass the cleaningJob object and isOwner boolean to the CleaningJobCard component and cleaningId to the CleaningJobEditForm component. At the moment, CleaningJobEditForm has its own logic on how to use that id to render its form properly, but that could probably be improved.

In fact, it would probably make sense to extract all of the logic within the CleaningJobEditForm into its own hook. Why don’t we do that now?

Befor we move on, here’s a codesandbox with all the code we’ve written up to this point:

Check it out on Code Sandbox

phresh-frontend-part-11-the-useSingleCleaningJob-hook

Check to make sure everything is in order.

Once that’s done, we’ll get to building our useCleaningJobForm hook.

The useCleaningJobForm hook

Go ahead and create a new file:

Could this file have gone in the hooks/cleanings directory? Sure. But it’s really more UI related, so we’re placing it there instead.

Since the same fields are being used by the CleaningJobCreateForm and CleaningJobEditForm components, we’ll design our hook to support both cases. As was the case with our LoginForm and RegistrationForm, there’s quite a bit of shared logic between the two. Fortunately, we won’t need to differentiate between the two situations explicitly like we did with our authentication form. We’ll just use a cleaningId to determine the form’s specifics.

Here’s how we’ll do it.

useCleaningJobForm.js

This hook is much larger in size, but is actually very similar to our useLoginAndRegistrationForm hook. The main difference is that we have some shared UI used in the cleaning type dropdown. Right beneath the imports, we have the cleaningTypeOptions array which we have extracted from both components and placed here. With this one simple change, we’ve already removed 45 lines of code from our app with no reduction in functionality. That’s a win.

We also take advantage of our freshly minted useSingleCleaningJob hook. The useCleaningJobForm takes in a single parameter - cleaningId - and passes it to useSingleCleaningJob to gain access to any currently existing cleaning jobs with that id. If the id doesn’t exist, then no cleaning job will be returned. No harm no foul.

However, notice that our form state defaults to all the attributes of the cleaningJob, if it exists. In the case that no cleaning is present, we use empty strings and the first value in the cleaningTypeOptions array. The useEffect hook below our state initializations is run each time the cleaningJob value changes. When it receives a valid cleanignJob, it updates the form state with whatever attributes the cleaning resource already has. By doing this, we have made our form flexible enough to support existing cleaning jobs and ones that are yet to be made. That is why we’ll be able to use this hook for both our CleaningJobCreateForm and CleaningJobEditForm components.

The rest of the hooks is code we’ve seen before, so we won’t dive into it any further.

Let’s see our work in action.

Open up the CleaningJobEditForm and make the following adjustments:

CleaningJobEditForm.js

Like the other form components, our new version has been reduced in size significantly.

And in fact, the same will be true of the CleaningJobCreateForm component. Open up that file and modify it accordingly:

CleaningJobCreateForm.js

A similar feel here. An almost 100 line reduction in code. That is the magic of using custom hooks. Encapsulated logic, reduced component size, and organized ui management.

Take the newly updated components for a spin. Create a new cleaning job and then edit it.

It works, huh?

Let’s put the finishing touches on our app by adding a success toast whenever the user updates a cleaning job.

Open back up the redux/cleanings.js file and add the following:

redux/cleanings.js

In the onSuccess hook for our updateCleaningJob action creator, we take advantage of our global ui toast list and create a success toast for when cleaning jobs have been successfully updated.

Go in an edit another post and watch the fruits of our labor in action.

Here’s a codesandbox with all the updates up to this point.

Check it out on Code Sandbox

phresh-frontend-part-11-the-useCleaningJobForm-hook

We have a lot of things that feel right here. Not everything works as before, however.

For instance, our cleaning jobs are still missing offers. We could return to our old method of fetching offers for a cleaning job when a component is mounted, but it probably makes more sense to populate cleaning jobs with their associated offers.

We’ll handle that in our next post as we head back to FastAPI land for some backend updates.

Wrapping Up and Resources

That should just about do it for our biggest frontend refactor yet. Our React application is becoming more organized and easier to reason about. We’ve also solidifed the core logic for handling cleaning jobs, so that we can move on to evaluations with confidence that the rest of our system is working effectively.

Next up, we’ll start populating cleaning jobs received from our backend with offers the authenticated user is allowed to access.

Tags:

Previous Post undraw svg category

Refactoring Our React UI Into Composable Hooks

Next Post undraw svg category

Populating Cleaning Jobs with User Offers in FastAPI