<header class="big-header">
  <h1>Rating Star Widget <br>with ReactJS</h1>
  <p>A simple rating widget using <b>ReactJS</b> and <b>FontAwesome (5!)</b> icons.</p>
  <p class="sub">Last I created the same widget using jQuery, it is one of my most popular pen. I thought why not create the same using React. Here it is.</p>
</header>

<div id="widget">
  
</div>



<footer id="credits">
  <p class="note"><b>Note:</b> This widget is not accessible, if you wish to use it in a real life application please do consider taking a look at  <a href="https://reactjs.org/docs/accessibility.html" target="_blank">ReactJS Accessibility Guide</a>. Also, check an accessible version of the widget here: <a href="https://codepen.io/depy/details/rJQyNK/" target="_blank">Accessible version</a></p>
  
  <p style="font-size: 14px;">Created with <i class="fas fa-heart" style="color: tomato;"></i> by Deepak Kamat (<a href="https://twitter.com/xxxdepy" target="_blank">@xxxdepy</a>)</p>
 
</footer>
body {
  font-family:"Open Sans", Helvetica, Arial, sans-serif;
  color:#555;
  max-width:680px;
  margin:0 auto;
  padding:0 20px;
}

* {
  -webkit-box-sizing:border-box;
  -moz-box-sizing:border-box;
  box-sizing:border-box;
}

*:before, *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}

a {
  color: tomato;
  text-decoration: none;
}

a:hover {
  color: #2196f3;
}

/*----------------------------
BIG HEADER
------------------------------*/
.big-header {
  padding: 2em 0;
  position: relative;
  text-align: center;
}

.big-header:after {
  content:"";
  display:block;
  height:1px;
  background:#eee;
  position:absolute; 
  left:30%; right:30%;
  bottom: 0;
}

.big-header h1 {
  font-size:2.75em;
  font-weight:300;
  margin-bottom:0.2em;
}

.big-header p {
  font-size: 16px;
}

.big-header p.sub {
  font-size: 12px;
  color: #9999;
}

.note {
  margin: 20px 0;
  padding: 10px;
  color: #888;
  background-color: #fefefe;
  font-size: 12px;
  border-left: 5px solid tomato;
}


/* Widget Container */
#widget {
  position: relative;
  padding: 20px 0;
  margin: 20px 0;
  border: 1px solid #eee;
  border-width: 0;
}


/*----------------------------
Rating Star Widget Style
-----------------------------*/
.rating-stars {
  position: relative;
  display: block;
  text-align: center;
}

.rating-stars .star {
  display: inline-block;
  padding: 0 5px;
  font-size: 2.5em;
  color: #ccc;
  
  transition: 0.1s all ease-in-out;
  transform: scale(0.8);
}
.rating-stars .star:hover,
.rating-stars .star.semi-active {
  color: #ffd73e;
  transform: scale(1);
}

.rating-stars .star.active {
  color: #FF9800;
  transform: scale(1);
}


/* Rating Message */
.after-rating-message {
  display: none;
  text-align: center;
  margin: 20px auto;
  padding: 20px 20px;
  background-color: #f7f7f7;
  opacity: 0;
  transition: 0.2s all ease-in-out;
}

.after-rating-message.show {
  display: block;
  opacity: 1;
}



function Star( props ){
  return (
    <div className={`star ${(props.value == 0) ? 'semi-active' : ''} ${(props.position <= props.rated) ? 'active' : ''} `} 
         onMouseEnter={ props.onMouseEnter }
         onMouseLeave={ props.onMouseLeave }
         onClick={ props.onClick }

    >
      <i className="fas fa-star"></i>
    </div>
  );
}

function Rating( props ){
  const messages = {
    "1": "Oh. Sorry you had a bad experience :( ",
    "2": "We will try to improve.",
    "3": "Appreciate it!",
    "4": "Thank you!", 
    "5": "You're Awesome!"
  };
  
  let rating = props.rating;
  
  return(
      <div className={"after-rating-message " + ((rating > 0) ? 'show': '')} >
          <span>You rated this {rating} star{rating > 1 ? 's' : ''}</span>
          <br/>
          <span>{ messages[rating] }</span>
      </div>
  );
}


class RatingWidget extends React.Component {
  constructor( props ) {
    super( props );
    this.state = {
      stars: Array(5).fill(-1),
      rated: 0
    };
  }
  
  handleMouseOver( i ) {
    let currentRating = this.state.rated;
    
    if ( currentRating > 0 ) {
      const hoverRatedStars = this.state.stars.slice();
      _.fill( hoverRatedStars, 0, currentRating, i );
      this.setState({ stars: hoverRatedStars });
    }
    else {
      const hoverStars = Array(5).fill(-1);
      _.fill( hoverStars, 0, 0, (i+1) );     
      this.setState({ stars: hoverStars});
    }
  }
  
  handleMouseOut() {
    let currentRating = this.state.rated;
    if ( currentRating > 0) {
      const resetRatedStars = this.state.stars.slice();
      _.fill( resetRatedStars, -1, currentRating, resetRatedStars.length );
      this.setState({stars: resetRatedStars});
    }
    else {
      const resetStars = this.state.stars.slice();
      _.fill( resetStars, -1, 0, resetStars.length );
      this.setState({stars: resetStars});
    }
  }
  
  handleClick( i ) {
    const clickedStar = this.state.stars.slice();
    
    _.fill( clickedStar, 1, 0, i );
    _.fill( clickedStar, 1, i, clickedStar.length );
      
    this.setState({
      stars: clickedStar,
      rated: i
    });
  }
  
  
  handleRating( rating ){
    return (<Rating rating={this.state.rated} />)
  }
  
  renderStar( i ){
    return (
      <Star 
        position={i}
        value={this.state.stars[i]} 
        rated={this.state.rated}
        onMouseEnter={ () => this.handleMouseOver(i) }
        onMouseLeave={ () => this.handleMouseOut() }
        onClick={ () => this.handleClick(i) }
        />
    );
  }
  
  render(){
    return (
      <div className='rating-stars-widget-outer'>
          <div className='rating-stars'>
            {this.renderStar(1)}
            {this.renderStar(2)}
            {this.renderStar(3)}
            {this.renderStar(4)}
            {this.renderStar(5)}
          </div>
        
          {this.handleRating( this.state.rated )}
      </div>
      
      
    );
  }
}


ReactDOM.render(<RatingWidget />, document.getElementById("widget"));
View Compiled
Run Pen

External CSS

  1. https://use.fontawesome.com/releases/v5.0.6/css/all.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-with-addons.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js
  3. https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js