mobile ecommerce development

Create a Star Rating Component

Reusing react components to build awesome components. You’ll create a star rating component that will handle collecting users’ ratings.

Step 1 Start by creating a folder called components in the src directory and add a rater folder inside that. Then add two files: star-rating.jsx and star-rating.css. In the star-rating.jsx file add the following contents:

import React, { Component } from 'react';

import './star-rating.css';

class StarRating extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentRating: this.props.currentRating
    };
  }

  componentDidMount() {
    this.setRating();
  }

  hoverHandler = ev => {
    const stars = ev.target.parentElement.getElementsByClassName('star');
    const hoverValue = ev.target.dataset.value;
    Array.from(stars).forEach(star => {
      star.style.color = hoverValue >= star.dataset.value ? 'yellow' : 'gray';
    });
  };

  setRating = ev => {
    const stars = this.refs.rating.getElementsByClassName('star');
    Array.from(stars).forEach(star => {
      star.style.color =
        this.state.currentRating >= star.dataset.value ? 'yellow' : 'gray';
    });
  };

  starClickHandler = ev => {
    let rating = ev.target.dataset.value;
    this.setState({ currentRating: rating }); // set state so the rating stays highlighted
    if(this.props.onClick){
      this.props.onClick(rating); // emit the event up to the parent
    }
  };

  render() {
    return (
      <div
        className="rating"
        ref="rating"
        data-rating={this.state.currentRating}
        onMouseOut={this.setRating}
      >
        {[...Array(+this.props.numberOfStars).keys()].map(n => {
          return (
            <span
              className="star"
              key={n+1}
              data-value={n+1}
              onMouseOver={this.hoverHandler}
              onClick={this.starClickHandler}
            >
              &#9733;
            </span>
          );
        })}
      </div>
    );
  }
}

export default StarRating;

Consume the Rating Component

Now that you’ve got a rating component, you’ll want to put it on a page. Create a folder in src called pages and inside that add a new rating folder with a rating-page.jsx and rating-page.css file.
The contents of the rating-page.jsx should be:

import React, { Component } from 'react';
import { withAuth } from '@okta/okta-react';

import { BrewstrRef } from '../../firebase';
import StarRating from '../../components/rater/star-rating';
import './rating-page.css';

class RatingPage extends Component {

  constructor(props) {
    super(props);
    this.state = {
      name: '',
      description: '',
      rating: 0,
      user: ''
    };
  }

  async componentDidMount(){
    const user = await this.props.auth.getUser();
    this.setState({user:user.email});
  }


  handleChange = ev => {
    this.setState({
      [ev.target.name]: ev.target.value
    });
  };

  setRating = rating => {
    this.setState({ rating: rating });
  };

  saveRating = () => {
    BrewstrRef.push()
      .set(this.state)
      .then(() => {
        this.props.history.push('/ratinglist');
      });
  };

  render() {
    return (
      <div className="rating-form">
        <div className="heading">Rate A Beer</div>
        <div className="form-input">
          <label htmlFor="name">Beer:</label>
          <input
            type="text"
            name="name"
            id="name"
            onChange={this.handleChange}
          />
        </div>
        <div className="form-input">
          <label htmlFor="description">Description:</label>
          <textarea
            name="description"
            id="description"
            onChange={this.handleChange}
          />
        </div>
        <div className="form-input rating">
          <label htmlFor="rating">Rating:</label>
          <StarRating
            numberOfStars="5"
            currentRating="0"
            onClick={this.setRating}
          />
        </div>
        <div className="actions">
          <button type="submit" onClick={this.saveRating}>
            Submit Rating
          </button>
        </div>
      </div>
    );
  }
}

export default withAuth(RatingPage);

 

Explanation of the above code

The import statements bring in the withAuth higher-order component from the @okta/okta-react package. This allows you to get the currently logged in user when saving ratings for that user. This also brings in the Firebase set up and the StarRating component.

At the bottom of the file, you wrap the RatingPage component with the withAuth higher-order component. This allows you to get the currently logged in user in the componentDidMount() function and add the user’s email address to the state. This will be saved with their ratings so that when they go to the RatingList page, they will only see their ratings.

The handleChange() function handles the changing of the text values for the beer name and description in the component’s form. The setRating() handler is what is passed to the rating component so that when a user clicks on a rating, the value is propagated back to the parent and, in this case, is added to the state.

The saveRating() function gets the reference to the Firebase store and pushes a new rating into the collection then the application is routed to the RatingList page.

The render() method is pretty standard except where you add the StarRating component. You set the numberOfStars to five for this rating system, then set the currentRating to zero. You could set it to two or three if you think that looks better. Finally, the reference to the click handler is passed to the StarRating component, so that when a user chooses a rating, the value is bubbled back up to the click handler on this page component.

Then the actual page component

import React, { Component } from 'react';
import { withAuth } from '@okta/okta-react';
import {BrewstrRef} from '../../firebase';
import './rating-list.css';

class RatingsListPage extends Component {

  constructor(props){
    super(props);
    this.state = {
      ratings: [],
      user:''
    };
  }

  async componentDidMount(){
    const user = await this.props.auth.getUser();
    BrewstrRef.orderByChild('user').equalTo(user.email).on('value', snap => {
      const response = snap.val();
      const ratings = [];
      for(let rating in response){
        ratings.push({id: rating, ...response[rating]});
      }
      this.setState({
        ratings: ratings
      });
    });
  }

  render(){
    return (
      <table className="ratings-list">
        <thead>
          <tr>
            <th>Beer</th>
            <th>Description</th>
            <th>Rating</th>
          </tr>
        </thead>
        <tbody>
        {this.state.ratings.map((rating) => {
          return (
            <tr className="rating" key={rating.id}>
              <td>{rating.name}</td>
              <td>{rating.description}</td>
              <td className="rating-value">{rating.rating}</td>
            </tr>
          )
        })}
        </tbody>
      </table>
    )
  }
}

export default withAuth(RatingsListPage);

This componentDidMount() is getting the currently logged in user and passing it to Firebase to get a list of the ratings that this user has entered. All queries to Firebase return a “snapshot” represented here by the variable snap and it is pushed onto an array and then the state is set with that array. If you push each “record” onto the array in the state object, the component will redraw each time one is pushed. That’s the reason you push onto another array and then only update the state once. The render() function merely lists the ratings in a table.

 

 

Add Routing to the Actual Components

All the routing is going to “fake” components that just spit out text right now. You’ll need to go back to the App.js file and make sure the routes are hooked to the components you just created. so that the final file contents are.

import React from 'react';
import { Link, Route } from 'react-router-dom';
import { SecureRoute, ImplicitCallback } from '@okta/okta-react';

import RatingPage from './pages/rating/rating-page';
import RatingsListPage from './pages/rating-list/rating-list';
import './App.css';

function App() {
  return (
    <div className="App">
      <nav>
        <Link to="/">Home</Link>
        <Link to="/rating">Rate</Link>
        <Link to="/ratinglist">My Ratings</Link>
      </nav>
      <main>
        <Route exact path="/" component={()=> 'Home Page')} />
        <SecureRoute exact path="/rating" component={RatingPage} />
        <SecureRoute exact path="/ratinglist" component={RatingsListPage} />
        <Route path="/implicit/callback" component={ImplicitCallback} />
      </main>
    </div>
  );
}

export default App;
Here, you just added the imports for the component pages you just created, then updated or added routes to those components.

I also added some styling to my menu in App.css in the src folder:

nav {
  background-color: #333;
  font-size: 1.5rem;
}

nav a {
  display: inline-block;
  color: white;
  padding: 1rem;
  text-decoration: none;
}

nav a:hover {
  background-color: black;
}

 

Get in touch