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.