We're going to start our tutorial with this O1 starting point directory. Now, that's where our initial application starts and let's go have a look at it right now. Now, once you've done npm install to install your dependencies, and npm run dev to run the development server on port 3000, you'll see the Donuts and Dragoons store in your browser.
This is a simple e-commerce application. We're selling t-shirts in this case. The list of products shown here is fixed. These are all shirts generated by mid-journey if you're curious. This is the main landing page, so it's going to show you all of the shirts that we have, and then you can simply click on one of the shirts and
go to the product details page. Now, the product details page is where you see the image as well as the title, the description, and the price. That's all static data, that's not going to change. There is the review section below, that is dynamic data, so we can add reviews,
and then there is the related products section at the bottom, where we see other shirts that we can then click on from here. This related product section is actually important to test, because what you want to do is make sure that your state management works when you navigate from the route that you're on to essentially the same route with a different parameter.
So in this case, we're going from one product to another, and you want to make sure that your state management system handles that correctly. In this case, that means updating the product in place, which it does, as well as getting the new set of reviews. Now, what we want to do in this tutorial is set up our cart. So we have a cart over here,
and we have two items in the cart currently. You can clear the cart, you can check out, can't really actually check out, but there's a button for it, and you can add the cart. So that's what we're going to implement here. Let's go back over to the code and take a look where we want to actually do this implementation. We'll start our tour of the code, the layout component.
That layout is shared across all of the routes of the application. In this case, it contains that header, which is the top of the page, as well as the children, which is the children for that given route. Now, at the top of this component, we're using GetCart to get the current contents of the cart. We also create a clear cart action that we're going to send down into our header.
That's how you can clear the cart. Now, these actions return a new cart. So in this case, we return an empty cart. Now, let's go take a look at that header. The header is a client component. It takes the current cart as well as that clear cart action. It then displays the number of items in the cart in that little circle.
It also either shows or hides the cart pop-up. The cart pop-up component is another client component. It again takes the cart, which you got from the header, as well as that clear cart action, again, from the header originating in the layout. It also has that clear cart button with a click handler, where you're going to want to call that clear cart action and
set the cart to whatever you get back from the clear cart action. Now, another important page here is the product detail page. That's under products ID and then page.tsx. Let's go have a look at that component. This product detail page takes the parameter of the ID of the product. It also creates an add to product server action
that it's then going to send on to the add to cart button. You can see the add to cart button right down here, right below the average rating. Then let's have a look at that add to cart button. It also takes the add to cart action server action. You're going to want to call that when you get a click, and then set the cart to the result of that action.
One last thing you'll probably need is the type for cart, which is defined over in API types. The cart is just an object that includes a products key. That products key is an array of different items in the cart. Our objective in this exercise is to create some React context that we will use to manage
that cart both on the client and on the server. Of course, check out the resources section for some helpful hints that will get you there. Good luck.
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.
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 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.
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.
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.
Let's dive in to the code.
Layout ComponentFirst, 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();
};
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} />
)}
...
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.
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:
Over in src/api/types.ts are type definitions, including one for Cart:
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.
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:
And an example provider component:
As well as a custom hook to access the context:
To recap, you'll need to alter these components:
Header component to display the cart countCartPopup component to display the contents of the cartAddToCart component to add an item to the cartAnd remember, the output of the addToCartAction and clearCartAction are the updated Cart object!
...
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 || [];
};
...
export interface Cart {
products: {
id: number;
name: string;
image: string;
price: number;
}[];
}
import React, { createContext, useState } from "react";
const useCounterState = () => useState<number>(0);
export const CounterContext = createContext<ReturnType<
typeof useCounterState
> | null>(null);
const CountProvider = ({ children }: { children: React.ReactNode }) => {
const [count, setCount] = useCountState();
return (
<CountContext.Provider value={[count, setCount]}>
{children}
</CountContext.Provider>
);
};
export const useCart = () => {
const cart = React.useContext(CartContext);
if (!cart) {
throw new Error("useCart must be used within a CartProvider");
}
return cart;
};