React Rating Star
Ratings are a common feature on websites, whether we're rating posts, games, movies, or products. That's why creating a rating component from scratch is a great idea. We can make use of useState
, onClick
, and mouse actions to handle interactions. Let’s get started!
First, in App.jsx, let's create a simple Star
component that renders an SVG icon. We'll apply Tailwind CSS classes for sizing and default color. In the latest version of Tailwind, size-8
is equivalent to w-8 h-8
, so we can use that for the star size.
const Star = () => {
return (
<svg
className={`size-8 text-yellow-500`}
aria-hidden='true'
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
viewBox='0 0 22 20'>
<path d='M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z' />
</svg>
)
}
Next, we’ll render 5 stars. While using a for
loop might seem like a good idea, it’s not the best choice in this case. For
loops are imperative, meaning we have to explicitly tell the program what to do step by step.
function App() {
const stars = [];
for (let i = 1; i < 5; i++) {
stars.push(
<Star
key={i}
/>
);
}
return (
<div className='flex justify-center items-center mt-32'>
<div className='flex items-center'>
{stars}
</div>
</div>
)
}
In React, coding can sometimes feel imperative, where we explicitly write out each step of the solution. However, the main focus should be on achieving the desired functionality, such as implementing the rating feature.
In functional programming, we use functions like map
to handle such tasks more elegantly. By using map
, we can achieve the same result with significantly fewer lines of code compared to an imperative approach.
function App() {
return (
<div className='flex justify-center items-center mt-32'>
<div className='flex items-center'>
{[...Array(5)].map((_, index) => (
<Star key={index} />
))}
</div>
</div>
)
}
This version is concise and clearly communicates the use of useState
for managing star ratings.
function App() {
const [rating, setRating] = useState(0)
console.log(rating);
return (
<div className='flex justify-center items-center mt-32'>
<div className='flex items-center'>
{[...Array(5)].map((_, index) => (
<Star key={index}
handleRating={() => setRating(index + 1)}
/>
))}
</div>
</div>
)
}
First, we use useState
to keep track of which star has been clicked. To determine how many stars should be filled, we use the following approach: filled={index < rating}.
This condition ensures that stars are filled based on the value stored in rating
, which represents the number of stars that should be filled.
import { useState } from 'react'
const Star = ({ handleRating }) => {
return (
<svg
onClick={handleRating}
className={`size-8 ${filled ? 'text-yellow-500' : 'text-gray-300'} `}
aria-hidden='true'
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
viewBox='0 0 22 20'>
<path d='M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z' />
</svg>
)
}
function App() {
const [rating, setRating] = useState(1)
console.log(rating);
return (
<div className='flex justify-center items-center mt-32'>
<div className='flex items-center'>
{[...Array(5)].map((_, index) => (
<Star key={index}
handleRating={() => setRating(index + 1)}
filled={index < rating}
/>
))}
</div>
</div>
)
}
export default App
Lastly, we’ll add a feature to fill the stars when hovering over them. For this, we’ll use React events like onMouseEnter
and onMouseLeave
.
First, we need to set up a new state called hovered
, initially set to null
. We also need to modify the filled
property to account for this state: filled={index < (hovered !== null ? hovered : rating)}
. This means that if hovered
is not null
, the number of filled stars is determined by the hovered
state. Otherwise, it falls back to the rating
state.
Additionally, we’ll create simple functions to handle mouse events and pass them as props to the Star
component.
import { useState } from 'react'
// import Star from './components/Star'
const Star = ({ filled, handleRating,handleHoverFill,handleHoverClear }) => {
return (
<svg
onClick={handleRating}
onMouseEnter={handleHoverFill}
onMouseLeave={handleHoverClear}
className={`size-8 ${filled ? 'text-yellow-500' : 'text-gray-300'} `}
aria-hidden='true'
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
viewBox='0 0 22 20'>
<path d='M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z' />
</svg>
)
}
function App() {
const [rating, setRating] = useState(0)
const [hovered, setHovered] = useState(null)
console.log(hovered)
return (
<div className='flex justify-center items-center mt-32'>
<div className='flex items-center'>
{[...Array(5)].map((_, index) => (
<Star
key={index}
handleRating={() => setRating(index + 1)}
filled={index < (hovered !== null ? hovered : rating)}
handleHoverFill={() => setHovered(index + 1)}
handleHoverClear={() => setHovered(null)}
/>
))}
</div>
</div>
)
}
export default App
Finally, we’ll move the code into a separate component. Once that’s done, we’ll have a fully functional star rating component!
import React from 'react'
const Star = ({ filled, handleRating, handleHoverFill, handleHoverClear, index }) => {
return (
<svg
onClick={() => handleRating(index)}
onMouseEnter={() => handleHoverFill(index)}
onMouseLeave={handleHoverClear}
className={`size-8 cursor-pointer ${filled ? 'text-yellow-500' : 'text-gray-300'}`}
aria-label={`Rate ${index + 1} stars`}
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
viewBox='0 0 22 20'>
<path d='M20.924 7.625a1.523 1.523 0 0 0-1.238-1.044l-5.051-.734-2.259-4.577a1.534 1.534 0 0 0-2.752 0L7.365 5.847l-5.051.734A1.535 1.535 0 0 0 1.463 9.2l3.656 3.563-.863 5.031a1.532 1.532 0 0 0 2.226 1.616L11 17.033l4.518 2.375a1.534 1.534 0 0 0 2.226-1.617l-.863-5.03L20.537 9.2a1.523 1.523 0 0 0 .387-1.575Z' />
</svg>
)
}
export default Star
import { useState } from 'react'
import Star from './Star'
const StarRating = () => {
const [rating, setRating] = useState()
const [hovered, setHovered] = useState(null)
return (
<div className='flex justify-center items-center mt-32'>
<div className='flex flex-col items-center gap-2'>
<div className='flex gap-1'>
{[...Array(5)].map((_, index) => (
<Star
key={index}
handleRating={() => setRating(index + 1)}
filled={index < (hovered !== null ? hovered : rating)}
handleHoverFill={() => setHovered(index + 1)}
handleHoverClear={() => setHovered(null)}
/>
))}
</div>
{rating && (
<h2 className='text-lg'>
Rating: <span className='font-semibold'>{rating}</span>
</h2>
)}
</div>
</div>
)
}
export default StarRating
import StarRating from './components/StarRaing'
function App() {
return (
<>
<StarRating />
</>
)
}
export default App
Code: https://github.com/marekgacek45/reactMaster/tree/main/starRating
See on YT: https://youtu.be/rE5vI_39uH8