Handling Reviews with React Context
Let's start by creating the ReviewsContext component at app/products/[id]/ReviewsContext.tsx.
Creating a ReviewsContext Component
ReviewsContext will be a client component because it needs to use context. Like before, we're going to bring in createContext and useState to manage our state, but this time we're going to bring in the Review type:
// inside app/products/[id]/ReviewsContext.tsx
"use client";
import React, { createContext, useState } from "react";
import { type Review } from "@/api/types";
We'll create our useReviewsState hook, which will take the initial reviews like we did with the initialCartState before:
const useReviewsState = (initialReview: Review[]) =>
useState<Review[]>(initialReview);
Next, we'll create the ReviewsContext which will use the output of the useReviewsState hook:
export const ReviewsContext = createContext<ReturnType<
typeof useReviewsState
> | null>(null);
For accessing this context, we'll define another custom hook called useReviews. If it doesn't find a context, then it will return an error.
export const useReviews = () => {
const reviews = React.useContext(ReviewsContext);
if (!reviews) {
throw new Error("useReview must be used within a ReviewProvider");
}
return reviews;
};
Finally, we'll create a ReviewsProvider that takes the initial reviews and uses the useReviewsState hook to initialize some state that we'll then pass down to any children using the ReviewsContextProvider:
const ReviewsProvider = ({
reviews: initialReviews,
children,
}: {
reviews: Review[];
children: React.ReactNode;
}) => {
const [reviews, setReviews] = useReviewsState(initialReviews);
return (
<ReviewsContext.Provider value={[reviews, setReviews]}>
{children}
</ReviewsContext.Provider>
);
};
export default ReviewsProvider;
Now we can bring the ReviewsProvider into our page.
Updating the Page to use ReviewsContext
Inside of page.tsx, we'll instantiate the ReviewsProvider with the product reviews inside of the ProductDetail return:
export default async function ProductDetail({
const addReviewAction = async (text: string, rating: number) => {
return reviews || [];
};
return (
<ReviewsProvider reviews={product.reviews}>
...
It's important to note that the only thing going to the client is the reviews, and none of the immutable data.
With ReviewsContext in place, we can remove the reviews prop from our Reviews and AverageRating components since they will now get this information from context:
<Reviews addReviewAction={addReviewAction} />
...
<AverageRating />
Updating the AverageRating Component
Over in AverageRating.tsx, we need to update the component to get the reviews from context.
To do this, we'll import the useReviews hook and remove the reviews prop in favor of getting the reviews from the hook:
"use client";
import { useReviews } from "./ReviewsContext";
export default function AverageRating() {
const [reviews] = useReviews();
...
We'll follow a similar process for the Reviews component.
Updating the Reviews Component
Inside of Reviews.tsx, import the useReviews hook and remove the reviews prop.
This time, we'll bring in reviews and setReviews from the hook:
export default function Reviews({
addReviewAction,
}: {
addReviewAction: (text: string, rating: number) => Promise<Review[]>;
}) {
const [reviews, setReviews] = useReviews();
...
Now in the form for adding a review, we'll use setReviews to update our reviews based on the output of calling addReviewAction with the text and rating:
...
<form
onSubmit={async (evt) => {
evt.preventDefault();
setReviews(await addReviewAction(reviewText, reviewRating));
setReviewText("");
setReviewRating(5);
}}
>
Checking Our Work
Back in the browser, when we navigate between routes, we can see that the reviews are changing from product to product.
We are also able to submit a new review and see the average rating update.
For example, adding a rating of 1 for the Wizard T-Shirt will lower the average rating to 3.3.
As one more check, let's check the the server output to make sure it includes reviews. This is important for SEO considerations.
When viewing the page source in the browser, searching for a a bit of the review text allows us to confirm that reviews are being rendered on the server:
<div class="mt-1 text-sm other-classes">
I really like this t-shirt, the wizard design is unique and the fabric is
soft. The only downside is that it's a bit too tight around the neck.
</div>
We've successfully integrated state management for reviews into our ecommerce app using React's Context API.
Next, we'll explore how to re-implement this app using Redux for both client-side and server-side rendering.