DEV Community

Yosra Skhiri
Yosra Skhiri

Posted on

Rating Stars in React js

Rating stars are a classic UI component used in most apps that implement an evaluating system. In this article, we're going to see how it could be done.
the following gif displays the final result of this tutorial, so keep reading 😊.

final demo

If you want to jump straight to the final code, you can skip all the explanation and get to the end of the article.

First, create the component folder in the src folder, the component folder is going to hold our RatingStars.js and the Star.js files. for the CSS, we will use a style.css file that will be imported in the App.js component, the style.css file resides in the src folder and will contain all the CSS rules needed.

In the RatingStars component, since we need 5 rating grades, I used an array to store those grades as strings like so:

const GRADES = ['Poor', 'Fair', 'Good', 'Very good', 'Excellent'];
Enter fullscreen mode Exit fullscreen mode

In the RatingStars component, I mapped through the GRADES array to display a star for every value and I passed the index of each value as a prop to the Star component. for the key prop, I passed the grade.
As mentioned in the documentation:

Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity.

If you want to read more about the key prop.
The h1 is meant to display the result of the review that is made by the user, it's going to stay empty for now.

We also added some CSS classes that we're going to write later.

import React from 'react';
import Star from './Star';

const RatingStars = () => {
    const GRADES = ['Poor', 'Fair', 'Good', 'Very good', 'Excellent'];

    return (
        <div className="container">
            <h1 className="result"></h1>
            <div className="stars">
                {
                    GRADES.map((grade, index) => (
                        <Star 
                            index={index} 
                            key={grade}
                        />
                    ))
                }
            </div>
        </div>
    );
}

export default RatingStars;
Enter fullscreen mode Exit fullscreen mode

Now let's make the star component, I used the star svg from iconsvg.
I associated every star with a radio button that has the index of the grade in the GRADES array as a value and grouped the SVG element and the radio input element by the label element.

import React from 'react';

const Star = (props) => {

    return (
        <label className="star">
            <input
                type="radio"
                name="rating"
                id={props.grade}
                value={props.index}
                className="stars_radio-input"
            />
            <svg 
                width="58" 
                height="58" 
                viewBox="0 0 24 24" 
                fill="none" 
                stroke="#393939" 
                strokeWidth="1" 
                strokeLinecap="round" 
                strokeLinejoin="round" 
            >
                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
        </label>
    );
}

export default Star;
Enter fullscreen mode Exit fullscreen mode

So far, this is what our app looks like:

output

Now, it's time to make it prettier. inside the styles.css file write the following classes:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
Enter fullscreen mode Exit fullscreen mode

we're using the universal selector to reset the padding and the margin and also set the box-sizing as a border box which will help us size the elements. For more info about this property see the MDN web docs

.container {
  padding: 16px;
  margin: 16px auto;
}
Enter fullscreen mode Exit fullscreen mode

The .container class takes care of the spacing.

.result {
  text-align: center;
  margin-bottom: 16px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
Enter fullscreen mode Exit fullscreen mode

The .result class is applied to h1 element in the RatingStars component.

.stars {
  display: flex;
  justify-content: center;
  gap: 8px;
}
Enter fullscreen mode Exit fullscreen mode

Concerning the stars class that wraps all the stars, we're using flex value for the display properly, which will display the flex items (stars) horizontally, with a gap of 8px between each star.

.star {
  position: relative;
  cursor: pointer;
}
Enter fullscreen mode Exit fullscreen mode

For every star, we added a pointer cursor to insinuate to the user that the star is clickable. The position relative is going to help us later position the radio button.

.stars_radio-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  clip: rect(1px, 1px, 1px, 1px);
}
Enter fullscreen mode Exit fullscreen mode

This is going to visually hide the radio button.

.stars_radio-input:checked ~ svg {
  fill: yellow;
}
Enter fullscreen mode Exit fullscreen mode

This is a temporary class that we're going to use to just verify if the star is checked or not.

Since we're going to display the rating result in the RatingStar component, we need to lift the state from the child component that is the Star.js to the parent component that is the RatingStar.js.
If you want to read more about lifting state: React Docs

To start making this work we need to declare a state in the parent component to store the grade's index:

const [gradeIndex, setGradeIndex] = useState();
Enter fullscreen mode Exit fullscreen mode

This is the function used for setting the state in the parent component, and that we're going to pass it to the child component as a prop.

    const changeGradeIndex = ( index ) => {
        setGradeIndex(index);
    }
Enter fullscreen mode Exit fullscreen mode

And this the function that we're going to use in Star.js component in order to update the state in the parent component.

const changeGrade = (e) => {
        props.changeGrade(e.target.value);
    }
Enter fullscreen mode Exit fullscreen mode

Also, we'll attach an onClick event on the radio button in Star.js that will trigger the changeGrade function.

onClick={changeGrade}
Enter fullscreen mode Exit fullscreen mode

Inside the h1 in RatingStars.js component, we used a ternary operator to display the value of the state only when the state is defined.

<h1 className="result">
   { GRADES[gradeIndex] ? GRADES[gradeIndex] : 'You didn\'t review yet'}
</h1>
Enter fullscreen mode Exit fullscreen mode

Now, this is how our app looks like:
demo

To make it behave more like the classic star rating UI component, we need to add the color yellow to the stars dynamically.
the activeStar object is declared and assigned in the RatingStars.

const activeStar = {
        fill: 'yellow'
    };
Enter fullscreen mode Exit fullscreen mode

Then, pass it as a prop to the Star.js, we also used a ternary operator here because we only want the clicked star along the previous stars starting from the left to have the color yellow.

style={ gradeIndex > index ? activeStar : {}}
Enter fullscreen mode Exit fullscreen mode

Add the style attribute to the svg element.

<svg
   width="58" 
   height="58" 
   viewBox="0 0 24 24" 
   fill="none" 
   stroke="#393939" 
   strokeWidth="1" 
   strokeLinecap="round" 
   strokeLinejoin="round" 
   style={props.style}
>
Enter fullscreen mode Exit fullscreen mode

Abd don't forget to delete the .stars_radio-input:checked ~ svg from the style.css, since we don't need it anymore.

Finally, this is the whole code:

src\components\RatingStars.js

import React, { useState } from 'react';
import Star from './Star';

const RatingStars = () => {
    const [gradeIndex, setGradeIndex] = useState();
    const GRADES = ['Poor', 'Fair', 'Good', 'Very good', 'Excellent'];
    const activeStar = {
        fill: 'yellow'
    };

    const changeGradeIndex = ( index ) => {
        setGradeIndex(index);
    }

    return (
        <div className="container">
            <h1 className="result">{ GRADES[gradeIndex] ? GRADES[gradeIndex] : 'You didn\'t review yet'}</h1>
            <div className="stars">
                {
                    GRADES.map((grade, index) => (
                        <Star 
                            index={index} 
                            key={grade} 
                            changeGradeIndex={changeGradeIndex}
                            style={ gradeIndex > index ? activeStar : {}}
                        />
                    ))
                }
            </div>
        </div>
    );
}

export default RatingStars;
Enter fullscreen mode Exit fullscreen mode

src\components\Star.js

import React from 'react';

const Star = (props) => {

    const changeGrade = (e) => {
        props.changeGradeIndex(e.target.value);
    }

    return (
        <label className="star">
            <input
                type="radio"
                name="rating"
                id={props.grade}
                value={props.index}
                className="stars_radio-input"
                onClick={changeGrade}
            />
            <svg 
                width="58" 
                height="58" 
                viewBox="0 0 24 24" 
                fill="none" 
                stroke="#393939" 
                strokeWidth="1" 
                strokeLinecap="round" 
                strokeLinejoin="round" 
                style={props.style}
            >
                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
            </svg>
        </label>
    );
}

export default Star;
Enter fullscreen mode Exit fullscreen mode

src\style.css

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.container {
  padding: 16px;
  margin: 16px auto;
}

.result {
  text-align: center;
  margin-bottom: 16px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

.stars {
  display: flex;
  justify-content: center;
  gap: 8px;
}

.star {
  position: relative;
  cursor: pointer;
}

.stars_radio-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  clip: rect(1px, 1px, 1px, 1px);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
hmem_hamza profile image
Hamza Hmem

keep going yossra, best wishes :D

Collapse
 
yosraskhiri profile image
Yosra Skhiri

Thank you! :D