ProNextJS

    Now it’s your turn! Try solving this exercise.

    Run locally (recommended)

    Code

    Start by cloning the project repository and follow instructions in the README.

    Or in browser

    Open on Gitpod
    Continue
    exercise

    Integrating User Reviews and Ratings into a Product Detail Page

    Jack HerringtonJack Herrington

    As a starting point, the CartContext with initial state has been copied into the 04-complete-react-state directory.

    Let's explore how to manage user-generated reviews and dynamically update average ratings in our app.

    Current State of Reviews in the Application

    When visiting a product page, you'll see a set of reviews. You also have the option of adding your own review by typing in some text and setting the rating.

    When submitting a new review, two updates occur:

    1. The review is added to the list of existing reviews.
    2. The average rating will be updated and displayed above the "Add to Cart" button.

    Examining the Code Structure

    The code for the product pages is in an RSC located at app/products/[id]/page.tsx.

    Here, the ProductDetail component takes the product id which is then used to fetch the corresponding product details.

    export default async function ProductDetail({
      params: { id },
    }: {
      params: { id: string };
    }) {
      const product = await getProductById(+id);
      const products = await getProducts();
      ...
    

    For reference, let's look at the Product information.

    The Product & Review Types

    A Product is an interesting mix of both mutable and immutable data.

    At src/api/types.ts, we can see the definition:

    export interface Product {
    	id: number;
    	image: string;
    	name: string;
    	price: number;
    	description: string;
    	reviews: Review[];
    }
    

    All of the properties except for the reviews are immutable.

    Each Review consists of a rating and text:

    export interface Review {
    	rating: number;
    	text: string;
    }
    

    The ProductDetail React Server Component will show the immutable data, and the mutable reviews data will be managed with client components.

    Utilizing Client Components

    Reviews are handled in the Reviews and AverageRatings client components.

    The Reviews component allows you to send a review by typing text that will be stored as local state and then sent to the server via the addReviewAction:

    export default function Reviews({
      reviews,
      addReviewAction,
    }: {
      reviews: Review[];
      addReviewAction: (text: string, rating: number) => Promise<Review[]>;
    }) {
      const [reviewText, setReviewText] = useState("");
      const [reviewRating, setReviewRating] = useState(5);
      ...
    

    The addReviewAction is defined in the parent page RSC and then passed down into the Reviews client component.

    // inside page.tsx
    
    const addReviewAction = async (text: string, rating: number) => {
      "use server";
      const reviews = await addReview(+id, { text, rating });
      return reviews || [];
    };
    
    ...
    <Reviews reviews={product.reviews} addReviewAction={addReviewAction} />
    

    The AverageRatings component takes in reviews and calculates the current average rating.

    // inside AverageRatings.tsx
    
    {
    	reviews && reviews?.length && (
    		<div className="mt-4 font-light">
    			Average Rating:{" "}
    			{(reviews?.reduce((a, b) => a + b.rating, 0) / reviews?.length).toFixed(
    				1
    			)}
    		</div>
    	);
    }
    

    Challenge

    For this exercise, your goal is to wire everything together by creating components for a ReviewsContext and ReviewsProvider that will send the reviews to the AverageRating and Reviews components.

    Note that every time the customer navigates between routes, the products/[id]/page.sx component will be re-created.

    As a hint, the output of the addReviewAction is the updated array of reviews.

    Transcript

    So I've copied our CART context with initial state application into a complete React state directory ahead of our going and figuring out how to do reviews. So let's go take a look at our reviews currently in the application. So when you take a look at any of these products, including this cool dragon T-shirt,

    you'll see a set of reviews. You can add your own reviews simply by typing your review in here, awesome shirt, as well as setting the rating and then submitting the review. Now, two things are going to happen. One, it's going to add that review and it's also going to update the average rating located right above the add to CART.

    So now let's go take a look at our code. The code for this starts over in the products ID route in the page RSC where we get the product detail component. It takes an ID and then we get the product information by that ID. Now, let's go take a look at that product information because that's an interesting mix of both immutable and mutable data.

    So we take a look at our types of a product type. It's got the ID, the image, name, price, and description. All of that is static and immutable data, and then it's got the list of reviews, which is going to be mutable data, and that has a rating and a text for each review.

    So this RSC is going to show that immutable data, the name, the image, the price, and so on, and then it's going to use client components to manage the reviews. Those client components include the average ratings component. That's the one that takes the reviews and then gives you

    a current average rating. It also includes the reviews component that not only shows the reviews, it also allows you to add a review by typing in some text, which it stores as local state, and then sends the server using the add review action. That add review action is defined in the parent RSC and then passed

    down into that reviews client component. Again, we're going to do this via React context and state. If you have any questions, be sure to refer to that resources section associated with this video. Good luck.