Simplify React Prop Management With A Context Hook
Bring the Context API into Hardware Handler to decrease our reliance on props.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the The newline Guide to Modernizing an Enterprise React App course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to The newline Guide to Modernizing an Enterprise React App, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:39] As I said when we first discussed context in the module introducing the various hooks and react, context is designed to share data that can be considered global for a tree or react components, such as the current authenticated user, a theme, etc. With context, instead of having to pass props from a parent component down through multiple children to reach the component that needs it, a react anti-pattern referred to as prop drilling, we can simply wrap the parent component's state with a context provider and call the values or functions we need in whichever child components need them with the help of the use context hook.
[00:40 - 00:52] So in this lesson, we'll create our first context component and call the use context hook to handle some of the checkout state in our code. So here's a good tip on how to know when to use context.
[00:53 - 01:11] You might be wondering to yourself, how to know when to use context in your own apps and it's a great question. Typically, when I see component props being passed through one or more components purely to reach more deeply nested children, that's a good indicator to me that context could be useful.
[01:12 - 01:27] If a direct descendant of a component needs state, passing it as a prop is probably fine, but much more than that and it's worth considering context. Don't worry, these next few lessons will help clarify how you can employ context to good effect.
[01:28 - 01:48] When reviewing the source code for this app, especially our root component of app.js, it appears that a lot of checkout related state is being passed as props to various child components. Specifically inside of the app component, I see the checkout count variable and the update checkout count function being passed to multiple components.
[01:49 - 02:03] While this is fine and works, it presents a good opportunity to employ reacts context API to make these values more globally available in the app to the components that need them without having to pass them as props. Ready to make it happen?
[02:04 - 02:20] As with our other new additions to the app like custom hooks, we want to keep our contexts organized. My preference for this, especially as some context can be used all over an application, is to have a top level folder inside of the main source folder just for context files.
[02:21 - 02:42] So the first thing that we'll do for our new checkout focused context is to make a folder inside of source that is named context. Once the context folder is there, go ahead and create a new file called checkout item context dot JS inside of this folder.
[02:43 - 03:00] This file is where we'll instantiate our context instance and define all the default values that this context will be responsible for. If you recall from earlier modules, default values are only used when a component does not have a matching context provider above it in the tree.
[03:01 - 03:16] Personally, I've not encountered the scenario in real world application development, but it's worth keeping in mind. I like to define these values inside of the file so it is easier for me to quickly check a context file and know at a glance what values should be available to me.
[03:17 - 03:26] Feel free to include or exclude it from your own context files as you get more comfortable with using context. So inside of our new file, go ahead and add the following code.
[03:27 - 03:39] First, we're going to import create context from react. Then we're going to declare a checkout item context.
[03:40 - 04:03] And finally, we will export our default checkout item context as well. So this checkout item context variable is the context object that we'll inject into our code where we define the state variables that this context will keep track of, the app component in this case.
[04:04 - 04:18] And as I said before, we can already see that the checkout count and the update checkout count functions are being passed around inside of app to its children. So let's add these two values to our new context file within the checkout item context variable.
[04:19 - 04:39] So inside of create context, we are going to add checkout count, which will give an initial value of zero, and update checkout count, which we will define as an empty arrow function. With that, we're ready to put this new context component into action.
[04:40 - 04:47] So we're going to add it to app.js. Naturally, the first thing that we'll want to do is import the context into this component.
[04:48 - 04:57] So add it up at the top of the component along with all of our other imports. Here's a little best practice aside.
[04:58 - 05:09] Keep CSS files at the bottom of a component's imports. It's considered a best practice to keep any CSS file imports at the bottom of a component for ease of understanding and consistency within your code.
[05:10 - 05:27] This is why you'll always see me adjust import order if something new happens to be auto imported into a file below a CSS file. So once checkout item context is imported, we'll create its provider object that allows consuming components to subscribe to context changes.
[05:28 - 05:46] The provider component accepts a value prop to be passed to consuming components that are descendants of this provider. Our provider will need the local checkout count and update checkout count variables, so we'll pass those into our checkout item context dot provider object as a comma separated list for its values.
[05:47 - 06:00] So here is what our JSX is going to end up looking like. We are going to wrap our checkout item context provider right after toast container around the rest of our application.
[06:01 - 06:25] So checkout item context dot provider, and then we will set value equal to double curly braces, and we'll have checkout count and update checkout count. And then at the very bottom, we will put in our closing provider and save that, let's put in your reformatted.
[06:26 - 06:33] Okay, here's another thing to be aware of. A single provider with a lot of values could become a performance issue for you .
[06:34 - 06:53] All consumers that are descendants of a particular context provider will re- render whenever any of the provider's value props change. If you have a lot of values inside of single context that gets updated by lots of different child components, this could become a performance issue causing the app to re-render too often.
[06:54 - 07:15] This may never be a problem for you, but if it does start to happen, one solution that I would recommend is splitting the single context into multiple smaller contexts, which could help reduce the frequency of unnecessary re-renders, but do not prematurely optimize. Only address this once performance actually starts to lag.
[07:16 - 07:43] Now that we have the checkout count value being provided by our checkout item context provider object, we no longer need to pass it as a prop to the navbar component within our apps JSX. So we can delete this value here, and then we can head over to the navbar component itself, and we can delete it from being imported as a prop.
[07:44 - 08:19] Now that we're in the navbar component, and it's time to start consuming our new checkout item context values, to consume context with in-functional components like ours, we'll need to import the use context hook into our app, and we'll also need to import the checkout item context itself. So the first thing that we'll do is scroll up to the top, and import use context from React, and then the next thing that we will do is import checkout item context from our application.
[08:20 - 08:52] It's also a best practice to import any dependency libraries like context, nav links, prop types, things like that, before you actually import locally created components and pieces of functionality, just as an aside. So for this particular component, since it previously had no React hooks, ES-lit automatically simplified it, going so far as to remove the return statement in curly braces, because they weren't necessary.
[08:53 - 09:04] We'll need to add them back to make accessing our use context hook possible. So wrap the JSX code here in curly braces, and add the return statement to the JSX to get us started.
[09:05 - 09:15] So we'll use the return and closing curly brace down here. Now do not let it reformat yet, because it will try to remove them again.
[09:16 - 09:46] We have already removed the destructured prop import of checkout count from the function initialization, and now we need to declare a new variable named checkout item context, which will access our imported checkout item context with the use context hook. So we will make a new checkout item context lowercase c to start with, and then we will call the use context hook and pass in the upper case checkout item context here.
[09:47 - 10:02] And then the return statement will stay in place, and things will be good. And finally, lower down in the JSX, replace the references we make to the old checkout count prop with checkout item context dot checkout count.
[10:03 - 10:16] Replace that and replace that. You might be wondering at this point, could you destructure checkout count from checkout context similar to how we did for state?
[10:17 - 10:31] And the answer is yes, absolutely. If you so desired, you could definitely just replace our const checkout item context with const checkout count inside of the nav bar component.
[10:32 - 10:47] I don't usually do this though, because it's less clear to someone else reading the code what values are coming from context versus state or simple props. I like code to be more explicit when possible, especially when working with a team of other developers on a code base.
[10:48 - 10:59] That's why when using context, I tend to make an explicit variable with the context's name instead of destructuring out just the variables I need. But decide what works best for you and your team.
[11:00 - 11:08] So I will revert that to checkout item context. At this point, this component refactor to use context should be done.
[11:09 - 11:18] Just remember to delete the prop types import and declaration at the bottom of the nav bar.js file as they are no longer needed. Delete prop types there.
[11:19 - 11:30] And delete prop types here because there are no longer props. Okay, let's head back to our app component and tackle the next checkout prop that we can replace with context.
[11:31 - 11:42] This happens to be the one where we list the product list component in the JSX. So delete the update checkout count prop being passed into the product list component.
[11:43 - 11:53] Then we'll turn our attention to the product list component to continue the ref actor. This component refactor won't be too dissimilar from the previous one we just did for nav bar.
[11:54 - 12:23] In our product list JS file, we'll add the use context hook and import the checkout item context hook. After that, remove the update checkout count prop from the function's instant iation and declare a new variable named checkout context underneath our other state variables at the top of the function.
[12:24 - 12:41] Finally, update the add item to checkout function where the update checkout count is referenced. And delete the now useless prop types library import at the top of this file and the declaration of props at the bottom of the function.
[12:42 - 13:01] That's two functions refactored to use the context API now. If we head back over to our app.js, we see that the third refactor for context with the checkout component is almost exactly the same as the one we did in the last section for product list.
[13:02 - 13:16] If you'd like to take a swing at this implementation of use context within checkout.js, please go ahead and try it now and then we'll do it together. Okay, here's how I'd implement use context for the checkout component.
[13:17 - 13:23] First, delete the update checkout count from the app.js. Then open up the checkout.js component.
[13:24 - 13:50] Once again, import the use context hook and the checkout item context at the top of this file. Remove the update checkout count prop and declare a new checkout item context variable under our custom hook declarations.
[13:51 - 14:08] And finally update the update checkout count inside of the remove item from checkout. And then delete the checkout prop types and the import because they no longer need to be there.
[14:09 - 14:24] At this point, you might be thinking we're done, but not so fast. While doing the refactor to use context in the checkout component, I couldn't help but notice that both this component and our app component use the use checkout hook.
[14:25 - 14:42] Yet they're also both accessing different values from that hook. And I wondered, is there any reason that we couldn't take the values from the child component's use of the use checkout hook, declare them in the parent, and then just use context to access whatever's needed, where it's needed.
[14:43 - 14:54] That way only one component would need to access the custom hook, but all the child components needing values would be able to get them via the use context hook. Let's give it a try.
[14:55 - 15:04] What do you say? If we have three new values from the use checkout hook in the checkout component, we're going to want to add those values to the checkout item context file.
[15:05 - 15:16] So inside of checkout item context, let's add some new functions and value placeholders to match the new values that we're going to be supplying from app. js.
[15:17 - 15:24] We're going to be supplying error, which will start as false. Check out items, which will start out as an empty array.
[15:25 - 15:41] And finally, that checkout items, which will be an empty arrow function. So since app is going to become the keeper of all of our use checkout values, update the destructuring of the hook to include these new values.
[15:42 - 15:56] Where we import here, we will now also include checkout items, set checkout items, and error. And inside the context provider, update the values that it is responsible for.
[15:57 - 16:19] So not only will we have checkout count and update checkout count, but we'll have checkout items, set checkout items, and error. Now we'll go back to the checkout component, delete the use checkout hook, because we will be passing those values through context, and fix all the pieces of the code that now need checkout item context added to them.
[16:20 - 16:38] Copy this and paste it where you see most of our yellow squiggly lines. We can also delete this import for the use checkout hook.
[16:39 - 16:58] And fix rjsx as well, as there are quite a few references to adhere to. Now we should be ready to retest our app with our new implementation of the use context hook.
[16:59 - 17:07] This lesson is almost at an end. As usual, start up your app locally if you haven't already, and give it a test to make sure everything still works.
[17:08 - 17:14] So I will switch over to my browser window. So if we did this correctly, our homepage should load fine.
[17:15 - 17:23] And then we can add a few new products to our checkout. That should still work too. Add that, add that, add this.
[17:24 - 17:33] Looking good so far. Let's try a little filtering. Okay, filtering is looking good. And last but not least, we should be able to see all these products in our checkout.
[17:34 - 17:45] If you'd like to, you can also test that the error scenarios work correctly by blocking the network calls in the Chrome DevTools. But I'm going to forgo that check this time. Things are looking good.
[17:46 - 17:59] I know that you know how to do it, and if not, just refer to a previous lesson in Module 4 or Module 5 to block network calls. At this point, we're done with our first lesson, adding context to hardware handler. Congrats!
[18:00 - 18:11] You've made it through another lesson, and we've added our first used context hooks to the app. We even got to consolidate where we use our custom use checkout hook at the same time. Not too shabby.
[18:12 - 18:31] In our next lesson, we'll focus on how we can refactor our app to use other contexts in other components to handle some of our checkout functions. [silence]