We begin our tutorial in the 01-starting-point-directory
, which as you can tell contains the starting point for our initial application.
After running npm install
to install your dependencies and npm run dev
to run the development server on port 3000, you should see the Donuts and Dragoons Store in your browser.
Donuts & Dragoons Overview
On the store's home page is a collection of all of the available shirts, which have been generated by Midjourney. Clicking a shirt will take you to a product details page.
The Product Details Page
The product details page includes the image, title, description, and price for a specific product. All this information is static and doesn't change.
At the bottom of the product details is a dynamic section where reviews can be added, along with a Related Products section.
Importance of the Related Products Section
The Related Products section is important to test.
It's important to make sure that state management works when navigating away from the current route to what is essentially the same route with a different parameter.
In this case, you want to make sure that your state management system handles navigating from one product to another correctly. This means updating the product in place and getting the new set of reviews.
The Cart
Throughout this tutorial, we'll be setting up the cart for our e-commerce application. Currently, there are two items in the cart.
You should be able to add items, clear the cart, and complete a mock "checkout" with state being managed appropriately.
Code Overview
Let's dive in to the code.
The Layout
Component
First, let's take a look at the Layout
component at src/app/layout.tsx
. This component is shared across all routes of the application.
The layout includes the Header
, as well as the children
for the given route:
...
<Header cart={cart} clearCartAction={clearCartAction} />
<main className="mx-auto max-w-3xl">{children}</main>
...
At the top of the component is some functionality related to the cart.
The cart
data comes from a call to getCart
, and code for the clearCartAction
will clear the cart. Both of these return an empty cart:
const cart = await getCart();
const clearCartAction = async () => {
use server;
return await clearCart();
};
The Header Component
Next, let's examine the Header
component at src/app/components/Header.tsx
.
This is a client component that takes the current cart and the clearCartAction
. It displays the number of items in the cart in a small circle and either shows or hides the CartPopup
component:
...
<span className="text-xl font-bold leading-10 text-gray-100">
{cart.products.length}
</span>
{showCart && (
<CartPopup cart={cart} clearCartAction={clearCartAction} />
)}
...
Cart Popup Component
The CartPopup
component is another client component that takes the cart
and the clearCartAction
that came from the Layout
by way of the Header
.
The component has a "Clear Cart" button that will call the clearCartAction
click handler, then set the cart to the value returned from the action.
Product Detail Page
Finally, let's take a look at the product detail page, which is located at src/app/products/[id]/page.tsx
.
The product detail page takes in the parameter of the id
of the product. It displays product information, and creates actions for adding items to the cart as well as reviews:
...
const addToCartAction = async () => {
"use server";
return await addToCart(+id);
};
const addReviewAction = async (text: string, rating: number) => {
"use server";
const reviews = await addReview(+id, { text, rating });
return reviews || [];
};
...
The Cart Type
Over in src/api/types.ts
are type definitions, including one for Cart
:
export interface Cart {
products: {
id: number;
name: string;
image: string;
price: number;
}[];
}
The Cart
is an object that includes a products
key, which is an array of different items in the cart. You'll be working with this cart type on both the client and server sides.
Challenge
Your objective in this exercise is to create some React Context that will manage the cart on both the client and the server.
This will require you to wire up the components we've looked at to be able to add an item to the cart, show the current cart count in the Header, display the cart contents in the popup.
You'll need to create a new CartContext
client component and add it to the layout.tsx
file.
Here an example React context:
import React, { createContext, useState } from "react";
const useCounterState = () => useState<number>(0);
export const CounterContext = createContext<ReturnType<
typeof useCounterState
> | null>(null);
And an example provider component:
const CountProvider = ({ children }: { children: React.ReactNode }) => {
const [count, setCount] = useCountState();
return (
<CountContext.Provider value={[count, setCount]}>
{children}
</CountContext.Provider>
);
};
As well as a custom hook to access the context:
export const useCart = () => {
const cart = React.useContext(CartContext);
if (!cart) {
throw new Error("useCart must be used within a CartProvider");
}
return cart;
};
To recap, you'll need to alter these components:
- The
Header
component to display the cart count - The
CartPopup
component to display the contents of the cart - The
AddToCart
component to add an item to the cart
And remember, the output of the addToCartAction
and clearCartAction
are the updated Cart
object!