In the State and event handling section, we learned the concept of state to manage data associated with a component. We have also used states in multiple places across our application to render components with dynamic data.
So far, the usage of state has been confined to the component in which it was declared. As the application grows, we will encounter scenarios where components need to share some states for rendering UI. In this module on state management, we will learn various techniques to share states between components by adding a feature to enable users to add products to their carts.
Let's get started by adding a button to add and remove products in the cart from the product listing page:
We can achieve this by creating a component named AddToCart that renders a button and toggles a state isInCart on button click.
Run the below command to create AddToCart component.
Add the AddToCart button to the end of the ProductListItem component:
Since the AddToCart button comes inside the <Link> component, clicking the button would take us to the product page. To avoid this, we can prevent the default navigation behaviour of the button and stop the click event from propagating to the <Link> component, by using preventDefault and stopPropagation methods on the event object:
Now, the button would work as expected, toggling the button texts on click event.
Next, we will show the count of products added to the cart in the header as shown:
To achieve this, one might consider adding a state in the Header component to track the cart items count and update it whenever the user interacts with the AddToCart button. However, React follows a unidirectional data flow, allowing data to pass only from a parent component to a child component as props. Looking at the component tree containing these components, you can understand that the Header component and the AddToCart component lack a parent-child relationship.
To address this challenge, we'll employ a common pattern in React known as Lifting state up. In React, when a state needs to be shared among components, we lift the state to their closest common ancestor.
In this scenario, both the Header and AddToCart components require access to data related to products in the cart. To facilitate data sharing between these components, we'll create a state inside their common ancestor—the ProductList component.
Instead of keeping track of the number of items in the cart in the state, we will store the slugs of cart items. This approach will make it easier to monitor the items added to the cart when we need to buy them in future.
Let's see this in action.
First, create a state cartItems to store the slugs of cart items in the ProductList component:
Since the Header component only needs to know the count of cart items, we can pass the length of the cartItems state as a prop:
Inside the Header component, we can display the cartItemsCount beside a cart icon in top right corner. To incorporate the cart icon from react-icons, first, run:
Then make changes to the Header component as follows:
Next, we will pass a boolean prop called isInCart from the ProductList component to the AddToCart component through the ProductListItem component. This prop will indicate whether a product has been added to the cart.
We can then define a function, toggleIsInCart, within the ProductList component. This function will add and remove items from the cart based on whether the product is already present.
Rather than directly passing the toggleIsInCart function to the ProductListItem, we'll pass an anonymous function, which in turn calls the toggleIsInCart function with the corresponding product slug. This approach avoids passing the slug prop to the AddToCart component:
Now, pass down the isInCart and toggleIsInCart props to the AddToCart component from ProductListItem:
At last, we can remove the state in the AddToCart component and use the props passed via ProductListItem:
With these changes, you'll notice that the cart items count in the header dynamically updates as you add or remove items from the cart.
The below diagram illustrates the flow of props from the ProductList component to the Header and AddToCart components:
While lifting state to share states between components, you may encounter situations where you want to pass props through numerous layers of components, even though the intermediate components don't have any use with them. This scenario, termed prop drilling, hampers the readability and maintainability of the code. In the next couple of lessons, we'll explore strategies to address prop drilling.
Let's commit the new changes:
You can verify the changes here.