How To Build a Star Rating Using useState Hook

How To Build a Star Rating Using useState Hook

Learn how to create a star rating widget using the useState hook in React

Have you ever wondered how star ratings commonly seen on e-commerce websites or app stores are created?

Thousands of reviews are received every day, and many products and services across the web aren't complete without a star rating for receiving customer feedback and reviews.

Using this guide, you should be able to understand the simple process of building a star rating with React.

Prerequisites

Basic understanding of React and useState hook.

To complete this tutorial, you’ll need:

  • Node.js - To create a local development environment.
  • React - To bootstrap the project.

Here is what a star rating component looks like:

star rating

Setting up the Project

Generate a React App using create-react-app:

npx create-react-app star-rating

Install aStar icon as dependencies:

npm install react-icons

You can also use an SVG in place of the react-icons

Change into the new project directory:

cd star-rating

Now, you can run the React application:

npm start

Then, visit localhost:3000 in your web browser to see the React app.

1. Creating the Star component

In the src, create a Star.jsx file and populate it with the following:

import React from "react";

const Star = () => {

  return (
    <div className="star">
    STAR 
    </div>
  );
};

export default Star;

Now, import the Star icon from react-icons:

import { FaStar } from "react-icons/fa";

Substitute the mockup text STAR with the JSX <FaStar />:

import React from "react";
import { FaStar } from "react-icons/fa";

const Star = () => {

  return (
    <div className="star">
        <FaStar />
    </div>
  );
};

export default Star;

This JSX displays a single star derived from react-icons.

2. Rendering the Star component

Remove default logic found in the App.jsx and import the Star.

import Star from "./Star";

function App() {
  return (
    <div className="App">
      <Star />
    </div>
  );
}

export default App;

Now, you should see a star rendered in your app via localhost:3000.

3. Adding useState Hook to Star component

Here, we create two states to manage the star hover and rating states. useState hook allows handling of the state of the Star component.

import { useState } from "react";

const [rating, setRating] = useState(null);
const [hoverFill, setHoverFill] = useState(null);

4. Creating multiple copies of Star icon

By default, star ratings are five. so, let's implement five stars by mapping through the existing single star. [...Array(5)] creates an array of length 5. Passing a map function to this array return 5 different star icons.

     {[...Array(5)].map((_, index) => {

        return (
         <button>
            <FaStar />
          </button>
        );

Noticed the use of (_, index), this is because we are not returning any item but making use of pre-existing one. The index act as a unique identifier for the stars.

5. Handling hoverFill and rating states

The Star icon from react-icons doesn't support eventhandling. So, we need any <html> tag that has inbuilt functionalities for eventhandlers. Wrap the Star icons within a <button> tag and control the state using specific events.

The hoverFill state is toggled by onMouseEnter and onMouseLeave props. We add a rating to the hoverFill state whenever we hover over the stars and also set it to null when the mouse is moved away from it. TheonClick function sets rating to the number of star rating.

 <button
      key={index}
      onMouseEnter={() => setHoverFill(ratingValue)}
      onMouseLeave={() => setHoverFill(null)}
      onClick={() => setRating(ratingValue)}
 >
        <FaStar />
 </button>

6. Setting the button wrapper visibility

Next, we will hide the visibility of the button to make the Star look better.

.star > button {
  background-color: transparent;
  border: none;
  cursor: pointer;
  outline: none;
}

7. Handling the star look

The onChange props handle the rating state after onClick event is passed. Using a conditional statement, we will check the hoverFill and rating to determine whether to fill the star with the yellow-like color or set it to default. The hoverFill decides the color of the star while the index value decides the number of stars to be filled.

Let's set the value of rating in ratingValue variable.

let ratingValue = index + 1;

index + 1 because the array starts from "0".

  <FaStar
       size={80}
       style={{
       color:
         ratingValue <= (hoverFill || rating) ? "#ffe101" : "#ccc",
        }}
       onChange={() => setRating(ratingValue)}
       value={ratingValue}
   />

8. Final step

The complete code of the Star component should look like this:


import React, { useState } from "react";
import { FaStar } from "react-icons/fa";

const StarRate = () => {
  const [rating, setRating] = useState(null);
  const [hoverFill, setHoverFill] = useState(null);

  return (
    <div className="star">
      {[...Array(5)].map((_, index) => {
        const ratingValue = index + 1;

        return (
          <button
            key={index}
            onMouseEnter={() => setHoverFill(ratingValue)}
            onMouseLeave={() => setHoverFill(null)}
            onClick={() => setRating(ratingValue)}
          >
            <FaStar
              className="FaStar"
              size={80}
              style={{
                color:
                  ratingValue <= (hoverFill || rating) ? "#ffe101" : "#ccc",
              }}
              onChange={() => setRating(ratingValue)}
              value={ratingValue}
            />
          </button>
        );
      })}
    </div>
  );
};

export default Star;

ratingValue <= (hoverFill || rating) ? "#ffe101" : "#ccc" implies that hoverFill and rating states of all the stars including the last one will be toggled with the fill color #ffe101 or returned to the default color #ccc as your mouse moves over them in the horizontal direction.

9. Bonus - Adding a Review label

To make the look aesthetic. Let's add a label for each rating.

Star ratings of 1,2,3,4,5 will display the review label of very bad, bad, okay, good, and excellent respectively.

  const getReviewLabel = (rating) => {
    switch (rating) {
      case 1:
        return `Very bad ${String.fromCodePoint("0x1F922")}`;
      case 2:
        return `Bad ${String.fromCodePoint("0x1F97A")}`;
      case 3:
        return `Okay ${String.fromCodePoint("0x1F60C")}`;
      case 4:
        return `Good ${String.fromCodePoint("0x1F60A")}`;
      case 5:
        return `Excellent ${String.fromCodePoint("0x1F929")}`;

      default:
        return "";
    }
  };

The Emojis used are gotten from Unicode emoji charts. Each Unicode emoji is converted to the equivalent hex value and used as a string parameter inString.fromCodePoint() method.

This is recommended because it is accessible by screen readers, unlike copy-paste emojis.

 <h2 className="review-label">
      {getReviewLabel(isHover > 0 ? isHover : rating)}
</h2>

The Star component's complete logic which includes the review label should now look like this:


import React, { useState } from "react";
import { FaStar } from "react-icons/fa";

const StarRate = () => {
   const [rating, setRating] = useState(null);
  const [hoverFill, setHoverFill] = useState(null);
  const [isHover, setIsHover] = useState(null);

  const getReviewLabel = (rating) => {
    switch (rating) {
      case 1:
        return `Very bad ${String.fromCodePoint("0x1F922")}`;
      case 2:
        return `Bad ${String.fromCodePoint("0x1F97A")}`;
      case 3:
        return `Okay ${String.fromCodePoint("0x1F60C")}`;
      case 4:
        return `Good ${String.fromCodePoint("0x1F60A")}`;
      case 5:
        return `Excellent ${String.fromCodePoint("0x1F929")}`;

      default:
        return "";
    }
  };

  return (
    <div className="star-wrapper">
            <h2 className="review-label">
              {getReviewLabel(isHover > 0 ? isHover : rating)}
            </h2>

            <div className="star">
              {[...Array(5)].map((_, index) => {
                const ratingValue = index + 1;

                return (
                  <button
                    key={index}
                    onMouseOver={() => setIsHover(ratingValue)}
                    onMouseOut={() => setIsHover(null)}
                    onMouseEnter={() => setHoverFill(ratingValue)}
                    onMouseLeave={() => setHoverFill(null)}
                    onClick={() => setRating(ratingValue)}
                  >
                    <FaStar
                      className="FaStar"
                      size={80}
                      style={{
                        color:
                          ratingValue <= (hoverFill || rating)
                            ? "#ffe101"
                            : "#ccc",
                      }}
                      onChange={(ratingValue) => setRating(ratingValue)}
                      value={ratingValue}
                    />
                  </button>
                );
              })}
            </div>
          </div>
  );
};

export default Star;

Add these Styling to the index.css file:

#root,
html,
body {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.App {
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  width: 100vw;
  height: 100vh;
}

.star-wrapper {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

.review-label {
  position: absolute;
  margin-top: -100px;
}

.star > button {
  background-color: transparent;
  border: none;
  cursor: pointer;
  outline: none;
}

@media screen and (max-width: 450px) {
  .FaStar {
    width: 40px;
  }
}

Conclusion

And with that being said, you have just learned how to create a star rating component with React. You can find the complete source code in this codesandbox.

Thank you very much for taking the time to read, I hope you found this helpful. Feel free to share and check other articles on my blog.