Contexts can be Used to Share Functions within an App Too
Context doesn't necessarily need to live at the top level of a component to be useful.
Context can be employed at all different levels of a React app, not just the root level#
Our last lesson was a big one: we made a big context Provider to wrap the entire application, and not only did we do that, but we also ended up moving some state in a child into the parent because they shared data from the same custom hook.
Now, we're about to dive deeper into Hardware Handler, and I'll demonstrate a couple of instances where we can employ smaller contexts to make our code cleaner and clearer.
Context doesn't just have to live high up in our React apps, though. It can also be small and nimble and very focused in its purpose, which is how we'll use it in this lesson.
Checkout API functions take center stage once more#
In our app, there are two instances where checkout API calls are passed from parent components: in the <ProductList>
component, where we display all the individual products that can be added to the checkout, and in the <Checkout>
component where each item rendered can be removed.
Now both of these components have a different checkout API call they contain and pass on to their own children.
We're going to take these components one at a time, create contexts for them so that we no longer need to pass the functions as props, and see how contexts can be employed all up and down an application in large and small ways.
Let's focus on the <Checkout>
component first.
Create a new CheckoutFunctionContext.js
file#
Inside of the <Checkout>
component is a function named removeItemFromCheckout
. This function is passed to the individual <CheckoutItem>
components that are rendered for each item present.
This is the function I want to make a context for.
const removeItemFromCheckout = async (id) => {
setLoading(true);
const remainingCheckoutItems = await checkoutApi.removeProductFromCheckout(
id
);
if (remainingCheckoutItems !== REMOVE_PRODUCT_FROM_CHECKOUT_ERROR) {
setCheckoutItems(remainingCheckoutItems);
await updateCheckoutCount();
toast.success(PRODUCT_REMOVED_FROM_CHECKOUT_SUCCESS);
} else {
toast.error(remainingCheckoutItems);
}
setLoading(false);
};
As with our previous context, inside of the context/
folder, create a new file named CheckoutFunctionContext.js
.
And create a context code shell in there.
import { createContext } from 'react';
const CheckoutFunctionContext = createContext({});
export default CheckoutFunctionContext;
Be specific in naming your contexts
I know the name for this new context file is long, but bear with me.
Since we've already got one context that's about checkout, we have to be specific in naming another one responsible for another piece of checkout. This is not an uncommon occurrence in large apps.
One of the hardest things about programming is naming variables, but don't be afraid to have long, specific names for files — your IDE will help with the autocompletion to make sure things stay spelled right after initial declaration.
Now, since we've already identified the function that we want this context to handle, we can add it as a default value inside of this new context file.
import { createContext } from 'react';
const CheckoutFunctionContext = createContext({
removeItemFromCheckout: () => {},
});
export default CheckoutFunctionContext;
After this, we're ready to add this context to our <Checkout>
component.
Add the new context to Checkout.js
#
Back over in our Checkout.js
file, we'll import the new CheckoutFunctionContext
object and wrap it as a Provider
around part of the JSX.
First, import the function at the top of the file:
import CheckoutItemContext from '../../context/CheckoutItemContext';
import CheckoutFunctionContext from '../../context/CheckoutFunctionContext';
import './Checkout.css';
Then, establish it as a Provider
in the file, and assign the local function of removeItemFromCheckout
as its value to replace our default value.
Contexts can work inside of specific pieces of JSX, not only the whole thing
Notice that I'm only wrapping the context provider around where we loop over the checkout items to render them.
This is one of the cool things about React's Context API — you don't need to wrap the whole JSX inside context for it to work, only the child components that will need to access the context object for those values.
<CheckoutFunctionContext.Provider
value={{ removeItemFromCheckout }}
>
<ul className="checkout-list-wrapper">
{checkoutItemContext.checkoutItems.map((item) => (
Delete the removeItemFromCheckout
prop#
Right after wrapping the JSX with the <CheckoutItem>
component, delete the removeItemFromCheckout
prop being passed to the component — it's no longer needed now that we'll be using context instead.
<CheckoutFunctionContext.Provider
value={{ removeItemFromCheckout }}
>
<ul className="checkout-list-wrapper">
{checkoutItemContext.checkoutItems.map((item) => (
<CheckoutItem key={item.id} item={item} />
))}
</ul>
</CheckoutFunctionContext.Provider>
Now we're ready to open up our <CheckoutItem>
component.
Update the CheckoutItem.js
file to access the useContext
Hook#
After adding the context Provider to the parent <Checkout>
component, it's time to add the consumer to the child <CheckoutItem>
component.
Inside of the CheckoutItem.js
file, we'll import the useContext
Hook and the CheckoutFunctionContext.js
context file.
import { useContext } from 'react';
import { formatPrice } from '../../helpers/formatPrice';
import CheckoutFunctionContext from '../../context/CheckoutFunctionContext';
import './CheckoutItem.css';
Then, remove the destructured prop of removeItemFromCheckout
from the component declaration, and declare a local variable of checkoutFunctionContext
set equal to the context we imported above wrapped in our useContext
Hook.
const CheckoutItem = ({ item }) => {
const checkoutFunctionContext = useContext(CheckoutFunctionContext);
And in the JSX below, in the <button>
element, update the onClick
function.
<button
className="primary"
onClick={() => checkoutFunctionContext.removeItemFromCheckout(item.id)}
>
Remove Product from Checkout
</button>
Great, we're making really good progress. Before we leave this component, though, your ESLint plugin should be going crazy.
Fix <CheckoutItem>
's ESLint errors#
At first glance, this CheckoutItem.js
file sets off all sorts of ESLint alarm bells, but it's really not all that bad — we can handle these issues.

The first issue is that our <button>
element in our JSX is missing an explicit type. This error is happening because the default value of type attribute for the button HTML element is "submit" which is often not the desired behavior and may lead to unexpected page reloads.
This page is a preview of The newline Guide to Modernizing an Enterprise React App