In the last lesson, we learned the concept of lifting state, where we moved a state to the nearest common parent and passed it down via props to share the state between components. However, passing props can become inconvenient, especially when the nearest common ancestor is far removed from the components that need data, and lifting the state that high can lead to a situation called prop drilling. Excessive prop drilling not only makes the code harder to manage but can also cause unwanted re-renders in components that receive props they don't actually use, potentially affecting the performance of the application.
Let's see how we can tackle the issue of prop drilling through an example.
At present, we can only add products to the cart from the product listing page. As the next step, consider adding this feature to the product details page. To make this possible, we can include the AddToCart
component in the Product
component. This will require us to lift the cartItems
state to the App
component so that the Header
and AddToCart
components within the Product
component can also access the necessary props.
In this scenario, we'll have to pass the props from the App
component through the component chain to reach the Header
and AddToCart
components, even though the intermediate components have no use of these props:
React provides a feature called context to avoid this hassle of prop drilling. Context lets the parent component make some information available to any component in the tree below it, no matter how deep, without passing it explicitly through props.
Let's understand the usage of context by adding the add-to-cart feature to the product details page.
As the first step, we will create a context for the cartItems
. Create a file CartItemsContext.js
inside the src/contexts
directory:
Inside this file, we will create and export a context named CartItemsContext
using the createContext
function from React. The createContext
function returns a context object:
In the App
component, we can create the cartItems
state and make it accessible throughout the application by wrapping the components with Provider
from the context. The Provider
component accepts a value
prop to specify the value of this context for all components inside it:
The value we passed to the context provider will be available to any component that comes under the context provider in the component hierarchy.
Now, we can use the cartItems
state and setCartItems
function from any component in our application using the useContext
hook from React. The useContext
hook accepts the context object as the argument and returns the context value.
Let's remove the cartItemsCount
prop from the Header
component. Instead, we can get the cartItems
from the context and find its length:
Next, we can remove the props isInCart
and toggleIsInCart
of the AddToCart
component. Instead, we can pass the slug
of the product from the ProductList
and Product
components to AddToCart
and use it along with the CartItemsContext
to check whether a product is in the cart and update the cart accordingly.
Now, you can remove the cartItems
state from the ProductList
component. Also, remove the toggleIsInCart
function and all its usages along with the usages of the isInCart
prop. You can use the AddToCart
component within the ProductListItem
and Product
components by passing the slug. Since the prop and its value has the same name, we can use the spread syntax mentioned in this lesson to pass the slug
prop.
After making the above changes, you should be able to add and remove a product from the product listing and product details page without prop drilling:
Since the AddToCart
component is now used by both the ProductListItem
and Product
components, let's move the AddToCart
component to the commons
directory:
Make sure to update the imports. Let's commit the new changes:
You can verify the changes here.