JavaScript Development Space

Create a Custom NextJS Star Rating Component From Scratch

Add to your RSS feedDecember, 14th 20234 min read
Create a Custom NextJS Star Rating Component From Scratch

Sure, creating a custom Star Rating component in Next.js involves creating a reusable React component that allows users to rate something using stars. Let's create a simple Star Rating component from scratch. We'll use React state to manage the rating and handle user interactions.

Prerequisites:

Before we begin, ensure that you have Node.js and npm (Node Package Manager) installed on your system. Additionally, you should have NestJS CLI installed globally. You can install it with the following command:

npm install -g @nestjs/cli

Installation

1. Setting Up the Project:

nest new rating

2. Add TailwindCSS:

npm install -D tailwindcss

Initialize the tailwind.

npx tailwindcss init -p

3. Configure your tailwind.config.js file

js
1 /** @type {import('tailwindcss').Config} */
2 module.exports = {
3 content: [
4 './app/**/*.{js,ts,jsx,tsx,mdx}',
5 './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 './components/**/*.{js,ts,jsx,tsx,mdx}',
7
8 // Or if using `src` directory:
9 './src/**/*.{js,ts,jsx,tsx,mdx}',
10 ],
11 theme: {
12 extend: {},
13 },
14 plugins: [],
15 };

4. Add Tailwind layers to the globals.css

css
1 @tailwind base;
2 @tailwind components;
3 @tailwind utilities;

5. Create a new folder named Rating in the components folder:

cd components && mkdir rating

6. Create 5 new files for Rating and Star Components:

Rating.tsx
Rating.props.ts
Rating.module.css
Star.tsx
Star.props.ts

7. Let's begin with the Star Component:

Modify the Start.props.ts file:

js
1 import { DetailedHTMLProps } from 'react';
2
3 export interface StarProps
4 extends DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
5 isFilled: boolean;
6 }

Here we are extending the DetailedHTMLProps to provide the all necessary props as onClick, onMouseEnter, onMouseLeave, TabIndex, and other.

Modify Star.tsx

js
1 /* eslint-disable max-len */
2 'use client';
3
4 import { StarProps } from './Star.props';
5
6 const Star = ({ isFilled = false, className, ...props }: StarProps) => {
7 return (
8 <span className={className}>
9 <svg
10 width='20'
11 height='20'
12 viewBox='0 0 20 20'
13 fill={isFilled ? '#FD7E14' : '#212121'}
14 xmlns='http://www.w3.org/2000/svg'
15 {...props}
16 >
17 <path
18 fillRule='evenodd'
19 clipRule='evenodd'
20 d='M13.4713 17.739C13.649 17.8405 13.8521 17.9167 14.0807 17.9167C14.4107 17.9167 14.7662 17.7643 14.9693 17.485C15.1725 17.2057 15.2486 16.8503 15.1725 16.5202L14.1314 12.0768C14.1314 12.0261 14.1568 11.9753 14.1822 11.9499L17.5084 8.9538C17.8639 8.64911 17.9908 8.16669 17.8385 7.73505C17.6861 7.30341 17.3053 6.99872 16.8482 6.97333L12.5572 6.64325C12.5064 6.64325 12.481 6.61786 12.4557 6.56708L10.856 2.40302C10.6783 1.94598 10.2721 1.66669 9.78964 1.66669C9.30722 1.66669 8.90097 1.94598 8.69785 2.40302L7.09824 6.56708C7.07285 6.61786 7.04746 6.64325 6.99667 6.64325L2.70566 6.97333C2.24863 6.99872 1.86777 7.30341 1.71542 7.73505C1.58847 8.16669 1.71542 8.64911 2.0455 8.9538L5.37167 11.9499C5.42246 11.9753 5.42246 12.0261 5.42246 12.0768L4.40683 16.5202C4.30527 16.8503 4.38144 17.2057 4.60996 17.485C4.81308 17.7643 5.14316 17.9167 5.49863 17.9167C5.72714 17.9167 5.93027 17.8405 6.13339 17.739L9.73886 15.3776C9.78964 15.3522 9.81503 15.3522 9.86582 15.3776L13.4713 17.739Z'
21 fill={isFilled ? '#FD7E14' : 'none'}
22 stroke={isFilled ? '#FD7E14' : '#212121'}
23 />
24 </svg>
25 </span>
26 );
27 };
28 export default Star;

8. Next step: modify the Rating.props.ts file:

js
1 import { DetailedHTMLProps } from 'react';
2
3 export interface StarProps
4 extends DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
5 isFilled: boolean;
6 }

9. New let's create the Rating.tsx file:

Rating.module.css

css
1 .rating {
2 @apply flex gap-2;
3 }
4 .filled {
5 @apply fill-yellow;
6 }

Rating.props.ts

js
1 import { DetailedHTMLProps } from 'react';
2
3 export interface RatingProps
4 extends DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
5 isEditable: boolean;
6 rating: number;
7 setRating?: (rating: number) => void;
8 }

Rating.tsx

js
1 import { useEffect, useState, KeyboardEvent } from 'react';
2
3 import { RatingProps } from './Rating.props';
4 import styles from './Rating.module.css';
5 import Star from './Star';
6
7 const Rating = ({
8 isEditable = false,
9 rating,
10 setRating,
11 className,
12 ...props
13 }: RatingProps): JSX.Element => {
14 const [ratingArray, setRatingArray] = useState<JSX.Element[]>(new Array(5).fill(<></>));
15
16 const hoverHandle = (idx: number) => {
17 if (!isEditable) {
18 return;
19 }
20 constructRating(idx);
21 };
22 const clickHandle = (idx: number) => {
23 if (!isEditable || !setRating) {
24 return;
25 }
26 setRating(idx);
27 };
28
29 const handleKeyDown = (e: KeyboardEvent<HTMLSpanElement>, idx: number) => {
30 if (!isEditable || !setRating) {
31 return;
32 }
33 if (e.code !== 'Space') {
34 return;
35 }
36 setRating(idx);
37 };
38
39 const constructRating = (currentRating: number) => {
40 console.log('currentRating', currentRating);
41 const updatedArray = ratingArray.map((ratingItem: JSX.Element, idx: number) => {
42 return (
43 <Star
44 isFilled={idx < currentRating}
45 key={idx}
46 onMouseEnter={() => hoverHandle(idx + 1)}
47 onMouseLeave={() => hoverHandle(rating)}
48 onClick={() => clickHandle(idx + 1)}
49 className={`${className || ''} ${isEditable ? 'cursor-pointer' : ''}`}
50 tabIndex={isEditable ? 0 : -1}
51 onKeyDown={(e: KeyboardEvent<HTMLSpanElement>) => handleKeyDown(e, idx + 1)}
52 />
53 );
54 });
55 setRatingArray(updatedArray);
56 };
57
58 useEffect(() => {
59 constructRating(rating);
60 }, [rating]);
61
62 return (
63 <div className={styles.rating} {...props}>
64 {ratingArray.map((r: JSX.Element, idx) => (
65 <span key={idx}>{r}</span>
66 ))}
67 </div>
68 );
69 };
70 export default Rating;

10. Use the Rating component in your app router. Open app/page.tsx and import and use the component:

We need also to provide the setRating function to the component.

js
1 'use client';
2 import { useState } from 'react';
3
4 import Rating from '@/components/ui/Rating/Rating';
5
6 export default function Home(): JSX.Element {
7 const [rating, setRating] = useState(3);
8 return (
9 <main>
10 <Rating isEditable={true} setRating={setRating} rating={rating} />
11 <Rating isEditable={false} rating={4} />
12 </main>
13 );
14 }

11. Run your Next.js app:

npm run dev

React rating component React rating component

Visit http://localhost:3000 in your browser, and you should see your custom Star Rating component.

This example is a simple representation of a star rating component. You can customize it further by adding styles, animations, and additional features based on your requirements.

Related Posts:

Implementing Dark Mode in NextJS14 with Tailwind CSS

To set up dark mode with Node.js 14 and Tailwind CSS, you'll follow a similar approach as before, but with some adjustments for server-side rendering in Node.js.

JavaScript Development Space

Follow JavaScript Development Space

Stay updated with the latest trends, tutorials, and best practices in JavaScript development. Follow our JavaScript Development blog for expert insights, coding tips, and resources to enhance your web development skills.

© 2024 JavaScript Development Blog. All rights reserved.