Product reviews are an integral aspect of an e-commerce store's branding and marketing. They help build trust and loyalty among customers and help drive sales.
In this article, we‘ll learn how to create a product review application with Next.js and Cloudinary's Upload widget.
Sandbox
The completed project is on CodeSandbox. Fork it and run the code.
The source code is also available on GitHub.
Prerequisites
To follow along with this article, we need to have:
- Knowledge of React and Next.js.
- A Cloudinary account.
- Understanding of Chakra UI, as the demo's user interface is built with it.
Getting started
Create a Next.js project by running the command below in our terminal.
1npx create-next-app product-review-app
Next, navigate into the project directory.
1cd product-review-app
Then, run the command below to start the application.
1npm run dev
We will use react-hook-form later in the article, so install that also.
1npm i react-hook-form
What we will create
We will start by creating the user interface below:
Clicking the 'Review Product' button will cause the modal containing the Form
component to pop up:
To effectively manage changes in the application, we need to set up state management using the Context API. The application will have two contexts: ImageUploadContext
and ReviewsContext
.
The ImageUploadContext
will hold the image the user uploads, whereas the ReviewsContext
will contain the user's comment and the uploaded image.
Creating the image upload context
As stated earlier, this context will hold the url of the uploaded image.
ImageUploadContext.js
1import { createContext, useContext, useState } from "react";23 const ImageUploadContext = createContext("");4 export const useImageUploadContext = () => useContext(ImageUploadContext);56 export default function ImageUploadContextProvider({ children }) {7 const [uploadedImgUrl, setUploadedImgUrl] = useState(null);89 return (10 <ImageUploadContext.Provider value={{ uploadedImgUrl, setUploadedImgUrl }}>11 {children}12 </ImageUploadContext.Provider>13 );14 }
Here, we do the following:
- Import the required dependencies from React.
- Create the
ImageUploadContext
that will hold the image URL. - Set up the
ImageUploadContextProvider
that will wrap the root of the application. - Set up the
uploadedImgUrl
state and pass it as the provider's value along withsetUploadedImgUrl
. - Create and export a custom hook,
useImageUploadContext
from which we will access the data in theImageUploadContext
.
Creating the review context
Though similar in set up to the ImageUploadContext
, ReviewsContext
will hold the user's comment and the uploaded image.
ReviewsContext.js
1import { createContext, useContext, useState } from "react";23const ReviewsContext = createContext(null);4export const useReviewsContext = () => useContext(ReviewsContext);56export default function ReviewsContextProvider({ children }) {7 const [reviews, setReviews] = useState([8 { reviewText: "first review", reviewImage: "/product.webp" },9 ]);1011 return (12 <ReviewsContext.Provider value={{ reviews, setReviews }}>13 {children}14 </ReviewsContext.Provider>15 );16}
Here, we do the following:
- Import
createContext
,useContext
, anduseState
from React. - Create the
ReviewsContext
that will hold the comment and image url. - Set up the
ReviewsContextProvider
that will wrap the root of the application. - Set up the
reviews
state and pass it as the provider's value along withsetReviews
. Thereviews
state will hold an array of objects, and the objects will have two properties:reviewText
andreviewImage
.reviewText
is the user's comment, andreviewImage
is the uploaded image. - Create a custom hook,
useReviewsContext
, from which we will access the data in theReviewsContext
.
Creating the ProductCard
component
The ProductCard
component will depict how an e-commerce product card looks in real-world applications.
ProductCard.js
1import { Box, Heading } from "@chakra-ui/react";2 import Image from "next/image";34 export default function ProductCard() {5 return (6 <Box>7 <Box position="relative" w="full" h="70%">8 <Image src="/product.webp" alt="an img" />9 </Box>10 <Box display="flex" alignItems="center" h="90px" pl={6}>11 <Heading>My Product</Heading>12 </Box>13 </Box>14 );15 }
Here, we import Box
and Heading
from Chakra UI and Image
from Next.js and use them to create the card.
Creating the Review
component
The Review
component will hold the user's review comment and image.
Review.js
1import { Box, Text, Image } from "@chakra-ui/react";23 export default function Review({ review }) {4 return (5 <Box p={2}>6 <Text fontSize="2xl" mr={6}>7 {review.reviewText}8 </Text>9 <Image src={review.reviewImage} alt={review.reviewImage} />10 </Box>11 );12 }
Let's break down the code above:
- We import
Box
, Heading, andImage
from Chakra UI, and use them to create the comment's interface. - The component receives a
review
prop, which is an object containing thereviewText
andreviewImage
we set up earlier in theReviewsContext
. - We import
Box
,Heading
, andImage
from Chakra UI and use them to create the comment's interface. - The component receives a review prop, an object containing the
reviewText
andreviewImage
we set up earlier in theReviewsContext
.
Creating the ReviewsContainer
component
The ReviewsContainer
will contain the different reviews for a product.
ReviewsContainer.js
1import { Box, VStack } from "@chakra-ui/react";2 import Review from "@components/Review";34 import { useReviewsContext } from "context/ReviewsContext";56 export default function ReviewsContainer() {7 const { reviews } = useReviewsContext();89 return (10 <Box mt={10} rounded="md" border="1px" borderColor="gray.200" p={3}>11 <VStack spacing={5} align="flex-start">12 {reviews.map((review) => (13 <Review review={review} key={review.reviewText} />14 ))}15 </VStack>16 </Box>17 );18 }
Let's break down the code above:
- We import the
useReviewsContext
hook fromReviewsContext
and access thereviews
. reviews
is an array of objects, so we map through it and pass eachreview
to theReview
component we set up earlier.
Creating the FormModal
component
The FormModal
contains the Form
component.
FormModal.js
1import {2 Modal,3 ModalOverlay,4 ModalContent,5 ModalHeader,6 ModalBody,7 ModalCloseButton,8 useDisclosure,9 Button,10 Box,11 } from "@chakra-ui/react";12 import Form from "./Form";1314 export default function FormModal() {15 const { isOpen, onOpen, onClose } = useDisclosure();1617 return (18 <Box mt={4}>19 <Button onClick={onOpen}>Review Product</Button>20 <Modal isOpen={isOpen} onClose={onClose}>21 <ModalOverlay />22 <ModalContent>23 <ModalHeader>Review Product</ModalHeader>24 <ModalCloseButton />25 <ModalBody>26 <Form closeModal={onClose} />27 </ModalBody>28 </ModalContent>29 </Modal>30 </Box>31 );32 }
Here, we set up the modal and set the Form
component as the modal's content. We also pass the onClose
method to Form.
We will need onClose
to close the modal after submitting the form.
Creating the Form
component
The Form
component will hold the input field where the user can enter their review, a button that will trigger Cloudinary's Upload Widget, and another button to submit the form.
Form.js
1import { useForm } from "react-hook-form";2 import { FormControl, Input, Stack, Text, Button } from "@chakra-ui/react";34 export default function Form({ closeModal }) {5 const { handleSubmit, register } = useForm();67 function onSubmit(value) {8 console.log(value);9 //do something with form data then close the modal10 closeModal();11 }1213 return (14 <Stack spacing={4}>15 <Text>Please leave a review</Text>16 <form onSubmit={handleSubmit(onSubmit)}>17 <FormControl>18 <Input type="text" {...register("reviewText")} />19 <Button>upload image</Button>20 </FormControl>21 <Button colorScheme="blue" mt={3} type="submit">22 Submit23 </Button>24 </form>25 </Stack>26 );27 }
Here, we do the following:
- Initialize
react-hook-form
and import itsuseForm
hook. We will use react-hook-form to track the input field's value and handle the form submission. - Import
handleSubmit
andregister
fromuseForm
. - Register the input field with react-hook-form.
- Create an
onSubmit
function where we define how to process the form data; after submitting the form, we call thecloseModal
method.
Bringing it all together
Having created the required components, let's bring them into the index.js
file.
1import { Heading } from "@chakra-ui/react";2 import ReviewsContainer from "@components/ReviewsContainer";3 import ProductCard from "@components/ProductCard";4 import FormModal from "@components/FormModal";56 export default function Home() {7 return (8 <div>9 <Heading as="h1" mb={12}>10 A Cool Ecommerce Product Review App11 </Heading>12 <ProductCard />13 <FormModal />14 <ReviewsContainer />15 </div>16 );17 }
Integrating Cloudinary's Upload widget
We've created the app's interface, so now let's integrate Cloudinary's Upload widget.
Next.js provides a Script component that we can use to load third-party scripts in our application. We need the Script
component to load the upload widget's script.
_app.js
1import { ChakraProvider } from "@chakra-ui/react";2 import LayoutWrapper from "@layout/index";3 import Script from "next/script";4 import ReviewsContextProvider from "@context/ReviewsContext";5 import ImageUploadContextProvider from "@context/ImageUploadContext";67 function MyApp({ Component, pageProps }) {8 return (9 <ReviewsContextProvider>10 <ImageUploadContextProvider>11 <ChakraProvider>12 <Script13 src="https://upload-widget.cloudinary.com/global/all.js"14 type="text/javascript"15 strategy="beforeInteractive"16 />17 <LayoutWrapper>18 <Component {...pageProps} />19 </LayoutWrapper>20 </ChakraProvider>21 </ImageUploadContextProvider>22 </ReviewsContextProvider>23 );24 }25 export default MyApp;
Here, we:
- Import
Script
into the_app.js
file and load the widget's script. - Wrap our application with the
ReviewsContextProvider
andImageUploadContextProvider
.
Initializing the widget
Having integrated the widget, let's initialize it back in the Form
component.
Form.js
1import { FormControl, Input, Stack, Text, Button } from "@chakra-ui/react";2 import { useForm } from "react-hook-form";3 import { useReviewsContext } from "context/ReviewsContext";4 import { useImageUploadContext } from "@context/ImageUploadContext";56 export default function Form({ closeModal }) {7 const { reviews, setReviews } = useReviewsContext();8 const { uploadedImgUrl, setUploadedImgUrl } = useImageUploadContext();910 //widget initializer11 function showWidget() {12 window.cloudinary13 .createUploadWidget(14 {15 cloudName: "OUR-ACCOUNT-CLOUD-NAME",16 uploadPreset: "ml_default",17 },18 (error, result) => {19 if (!error && result && result.event === "success") {20 setUploadedImgUrl(result.info.thumbnail_url);21 }22 if (error) {23 console.log(error);24 }25 }26 )27 .open();28 }29 const { handleSubmit, register } = useForm();3031 //form submission handler32 function onSubmit(value) {33 setReviews([34 ...reviews,35 { reviewText: value.reviewText, reviewImage: uploadedImgUrl },36 ]);37 closeModal();38 }3940 return (41 <Stack spacing={4}>42 <Text>Please leave a review</Text>43 <form onSubmit={handleSubmit(onSubmit)}>44 <FormControl>45 <Input type="text" {...register("reviewText")} />46 <Button onClick={showWidget}>upload image</Button>47 </FormControl>48 <Button colorScheme="blue" mt={3} type="submit">49 Submit50 </Button>51 </form>52 </Stack>53 );54 }
Let's break down the code above:
- We import
ReviewsContext
andImageUploadContext
. We accessreviews
andsetReviews
fromReviewsContext
, anduploadedImgUrl
andsetUploadedImgUrl
fromImageUploadContext
. - We create a
showWidget
function that initializes the widget. We passshowWidget
to the image button'sonClick
handler. - We update the
onSubmit
function. Instead of logging the form data to the console, we pass that data to thereviews
state. Upon form submission, we add a new object to the state. We get thereviewText
from the input field and thereviewImage
from theuploadedImgUrl
state. After that, we close the modal.
With this, we have successfully created a product review application.
Conclusion
This article taught us to create a product review application with Next.js and Cloudinary's Upload widget.