Replacing Cart Context with Redux
Start by copying 03-cart-context-with-initial-state into 05-cart-with-redux:
After setting up the new project, let's return to our code editor.
Removing Cart Context & Setting Up Redux
The first thing we'll do is remove the CartContext component at app/components/CartContext.tsx to make sure we replace it completely with Redux.
Next, we need to add the necessary libraries for Redux:
redux: The core state management library.react-redux: Provides bindings between React and Redux, selectors, and the store provider.@reduxjs/toolkit: A library that simplifies building and maintaining Redux stores.
With the dependencies installed, we'll create a store directory inside of the app directory, and a new store.ts file that will be our Redux store.
Creating the Cart Slice & Store
Inside of store.tsx, we'll have two slices for the cart and the reviews.
Let's start by creating the cart slice.
First, we'll import the createSlice from Redux Toolkit, as well as our Cart type from @/api/types:
We'll define an interface for the CartState, and use createSlice to create a slice named cart with the initial state of an empty products array:
At this point, there are red squiggles inside of the cartSlice because we haven't added any reducers.
Reducers allow us to specify the actions for a given slice. Let's bring in some reducers.
In this case, we'll add the setCart action, which takes a cart and sets the cart in the store to that cart. We'll also need to import PayloadAction type in order to properly define the reducer:
Now that we have our slice set up, we need to create a store.
To do this, let's import configureStore from the Redux toolkit:
According to the ReduxJS toolkit documentation, you would typically build your store like this:
However, we want to avoid this because it creates a global variable!
Instead, we'll create a createStore function that in turn will call configureStore:
Next, we'll export some types for the StoreType, the RootState, and the AppDispatch.
The RootState is the most important type here, as it represents the structure of our store. Hovering over it shows us that it contains the cart with the CartState:
Finally, we will create a selector for useCart that will give us access to the cart.
The selector will use the useSelector hook from react-redux to get the cart from the store:
At this point, we have created our Redux store, and are ready to start using it!
Creating a Store Provider
Since we can't use Redux globally, we will use context with the built-in provider from react-redux to pass our store throughout the entire application.
First, we'll create a new StoreProvider.tsx file inside of the store directory.
Since we will be using context, the StoreProvider needs to be a client component. The component will need to create and hold the store, which we'll do with useRef. If the store ref doesn't exist, we'll create it with createStore and dispatch the setCart action with the initial state. Finally, the StoreProvider will return the children wrapped by the Provider from react-redux with the current store passed in as a prop:
Now that we have a working StoreProvider, we can update our components to use it.
Updating the Layout Component
In the Layout component at layout.tsx, import the StoreProvider and replace the existing CartProvider with it.
Where we used to use cart from the CartContext, we will now use cart from the store:
Updating the Cart Popup Component
Inside of CartPopup.tsx, we will replace the useCart import from CartContext with the useCart from the store.
We'll also need to import dispatch from react-redux in order to dispatch the setCart action which also comes from the store when the cart is cleared:
We'll get the cart from useCart(), and dispatch from useDispatch():
Then, when the "Clear Cart" button is clicked, we'll dispatch the setCart action with the output of the clearAction function:
Updating the Add to Cart Component
Over in AddToCart.tsx, we only need to bring in the setCart action from the store. We'll also import useDispatch from react-redux to dispatch the action when the button is clicked:
Recapping Our Work
Back in the browser we can test our updated components. The cart count updates correctly, and the cart pop-up displays the correct items.
Taking a look at the SSR result by viewing the page source, you'll notice a <span> tag with the number 2 nested inside, representing the current value of the cart.
The cart can also be cleared, and the count updates accordingly!
This confirms that the Redux store is being initialized before the server-side render, and the correct data is being passed to the client components.
Now that we've successfully created our Redux store and passed it around using a StoreProvider at the layout level, we're ready to tackle the next challenge of integrating the Reviews slice into our store.