Rating Stars in React js
Tags: react, tutorial
Published: 2020-11-25 19:50:59

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](https://dev-to-uploads.s3.amazonaws.com/i/q3ervvn1cqdzvpqj6f28.gif)
 
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:
```react
const GRADES = ['Poor', 'Fair', 'Good', 'Very good', 'Excellent'];
```
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](https://reactjs.org/docs/lists-and-keys.html#keys).
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.

```react
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;
```

Now let's make the `star` component, I used the star svg from [iconsvg](https://iconsvg.xyz/).
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. 
```react
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;
```
So far, this is what our app looks like:

![output](https://dev-to-uploads.s3.amazonaws.com/i/8hkl461hrhel9sc5g6zz.gif)

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

```css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
```
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](https://developer.mozilla.org/en-US/docs/Web/CSS/box-sizing)

```css
.container {
  padding: 16px;
  margin: 16px auto;
}
```
The `.container` class takes care of the spacing.

```css
.result {
  text-align: center;
  margin-bottom: 16px;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
```
The `.result` class is applied to `h1` element in the `RatingStars` component.

```css
.stars {
  display: flex;
  justify-content: center;
  gap: 8px;
}
```
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.

```css
.star {
  position: relative;
  cursor: pointer;
}
```
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.

```css
.stars_radio-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  clip: rect(1px, 1px, 1px, 1px);
}
```
This is going to visually hide the radio button.

```css
.stars_radio-input:checked ~ svg {
  fill: yellow;
}
```
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](https://reactjs.org/docs/lifting-state-up.html) 

To start making this work we need to declare a state in the parent component to store the grade's index:
```react
const [gradeIndex, setGradeIndex] = useState();
```

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.
```react
    const changeGradeIndex = ( index ) => {
        setGradeIndex(index);
    }
```

And this the function that we're going to use in `Star.js` component in order to update the state in the parent component.
```react
const changeGrade = (e) => {
        props.changeGrade(e.target.value);
    }
```

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

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.
```react
<h1 className="result">
   { GRADES[gradeIndex] ? GRADES[gradeIndex] : 'You didn\'t review yet'}
</h1>
```

Now, this is how our app looks like:
![demo](https://dev-to-uploads.s3.amazonaws.com/i/wqctvgur58zin218uw21.gif)

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`.
```react
const activeStar = {
        fill: 'yellow'
    };
```

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.
```react
style={ gradeIndex > index ? activeStar : {}}
```

Add the style attribute to the svg element.
```react
<svg
   width="58" 
   height="58" 
   viewBox="0 0 24 24" 
   fill="none" 
   stroke="#393939" 
   strokeWidth="1" 
   strokeLinecap="round" 
   strokeLinejoin="round" 
   style={props.style}
>
```

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`
```react
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;
```

`src\components\Star.js`
```react
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;
```

`src\style.css`
```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);
}
```