This post will discuss how to create a star rating display in React, and a star rating picker with color-changing hover effects similar to Yelp.com's version.
Yelp's star rating display rounds to the half star:


Yelp has over 5 million claimed businesses. Assuming an even distribution of average star ratings, rounding down from .2 and up from .8 suggests Yelp has over 2 million worth of fractions of stars either taken away or falsely awarded.
The version we will build here rounds to the tenth. It could go even further than this, but it wouldn't make a difference visually:

Star Rating Display
Overview
- Get the average star rating. This will be converted to an array.
- Calculate the number of full stars, the decimal/fraction of the partial star, and the number empty stars
- Push them into an array of 5, with 1s for full stars, a decimal for the partially filled star (like 0.3), and 0s for empty stars
- Map the array into divs and set the background color to a linear gradient that changes from your fill color (orange here), to an empty color (gray), based on the values in the array
- The 1s in the array become 100% full of the fill color, the partial star becomes partially full of the fill color based on the decimal, and the empty stars are 0% full of the fill color and only colored with the empty color
Code
const fullStars = Math.floor(starAverage);
const starArr = [];
for(let i = 1; i <= fullStars; i++)
{
starArr.push(1);
}
if(starAverage < 5) {
const partialStar = starAverage - fullStars;
starArr.push(partialStar);
const emptyStars = 5 - starArr.length;
for(let i=1; i<=emptyStars; i++) {
starArr.push(0);
}
}
const stars = starArr.map((val, i) => {
return <div key={i}
className={style.starBox}
style={{background: `linear-gradient(90deg, #ff643d
${val * 100}%, #bbbac0 ${val * 100}%)`}}>★</div>
})
The last block starting with const stars = takes the array of 1s, a decimal, and zeros we created and maps it to 5 div elements. The stars variable is then used later in the return statement of the React component where we want to show the star rating. The key to getting the color fill right is the style attribute.
style={{background: `linear-gradient(90deg, #ff643d ${val * 100}%, #bbbac0 ${val * 100}%)`}}>★</div> is setting the background color of each div using a linear gradient.
The first argument, 90deg, means the gradient color change is happening from left to right across the div. The next argument, #ff643d ${val * 100}%, is saying we start at the color orange, and continue right with solid orange until a certain percentage across. The percentage is calculated from val, which is the 1, decimal or zero coming from the starArr array. So if it is a 1, it colors orange 100% across the div for a full star. For a decimal, like 0.3, it will color orange 30% across the div.
The next argument, #bbbac0 ${val * 100}, is another color stop using gray, and the same calculated percentage. This just means that there is no smooth transition between colors because the percent value is the same as the last color stop, so there is an immediate change from orange to gray. Read the MDN linear gradient docs to learn more.
If the value getting mapped is a 0, then there will be no orange in the background of the div and only gray, because the switch from orange to gray happens at 0% of the way across the linear gradient.
Star Rating Picker

This is built in a similar way with some changes. The entire react component with commented explanations is below:
import React, { useEffect } from 'react';
import style from './StarRatingPicker.module.css';
export default function StarRatingPicker({ rate, rating, changeColor, color,
parent }) {
const colors = ['#FFD56A', '#FFA448', '#ff7e42', '#ff523d', '#f43939'];
useEffect(() => {
const i = colors[Math.floor(rating - 1)];
changeColor(i)
}, [])
function hoverRating() {
if (rating === 0) {
return "Select your rating"
}
else if (rating === 1) {
return "Not good"
}
else if (rating === 2) {
return "Could've been better"
}
else if (rating === 3) {
return "OK"
}
else if (rating === 4) {
return "Good"
}
else if (rating === 5) {
return "Great"
}
}
const fullStars = Math.floor(rating);
const starArr = [];
for(let i = 1; i <= fullStars; i++)
{
starArr.push(1);
}
if(starAverage < 5) {
const partialStar = starAverage - fullStars;
starArr.push(partialStar);
const emptyStars = 5 - starArr.length;
for(let i=1; i<=emptyStars; i++) {
starArr.push(0);
}
}
const starRatingPicker = starArr.map((val, index) => {
return <div key={index}
className={style.starBox}
onClick={() => rate(index + 1)}
onMouseEnter={() => {
rate(index + 1);
changeColor(colors[index]);
}
// Again mapping the array to divs as in the previous example.
// Here we add an onClick listener to call a rate function that
// sets a rating state, which is used in a post to the server to
// create the actual rating.
// ChangeColor sets a color state from the colors array at the
// top of the component, which will be used below
}
style={{background: `linear-gradient(90deg, ${color}, ${color}
${val * 100}%, #bbbac0 ${val * 100}%)`}}>★</div>
})
return <div className={style.rating + ' ' + parent}>{starRatingPicker}
<p className={style.hoverText}>{hoverRating()}</p>
</div>
}