ProNextJS
    State Management with Next.js App RouterLevel up with State Management with Next.js App Router

    I respect your privacy. Unsubscribe at any time.

    This is a free tutorial

    In exchange for your email address, you'll get full access to this and other free ProNextJS tutorials.

    Why? First and foremost, your inbox allows us to directly communicate about the latest ProNextJS material. This includes free tutorials, NextJS Tips, and periodic update about trends, tools, and NextJS happenings that I'm excited about.

    In addition to the piles of free NextJS content, you'll get the earliest access and best discounts to the paid courses when they launch.

    There won't be any spam, and every email you get will have an unsubscribe link.

    If this sounds like a fair trade, let's go!

    exercise

    Add Cart Support with React Context

    Jack HerringtonJack Herrington

    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!

    Transcript

    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.