Voting Functionality

In notebook:
FrontEndMasters Serverless
Created at:
2017-10-12
Updated:
2017-10-12
Tags:
backend JavaScript libraries React

Let's add voting!

  //	****		Restaurant.js		****

import React, { Component, PropTypes } from 'react';
import map from 'lodash/map';
import './Restaurant.css';

class Restaurant extends Component {
  render () {
    const { name } = this.props
    return (
      <article className="Restaurant">
        <h3> { name } </h3>
        // **** 1 add the button.  ↴
        <button>
          Yea, I'd go there
        </button>
      </article>
    );
  }
}

Restaurant.propTypes = {
  name: PropTypes.string,
  votes: PropTypes.object,
  user: PropTypes.object,
  handleSelect: PropTypes.func,
  handleDeselect: PropTypes.func
};

export default Restaurant;

We need a key to identify an individual restaurant, to make the button work. We already pass a key from the parent component, but this is only accessible for React to do its optimisations.

We'll do a temporary solution and pass the voted/not voted property from Restaurants. This can be made even nicer with Redux later...

  //	****		Restaurants.js		****

import React, { Component, PropTypes } from 'react';
import Restaurant from './Restaurant';
import map from 'lodash/map';
// **** (3. import the database).  ↴
import { database } from './firebase'
import './Restaurants.css';

class Restaurants extends Component {
  constructor(props) {
    super(props);
  }
  
  // **** 2 handle selecting.  ↴
  handleSelect(key) {
    database.ref('/restaurants')
      .child(key) // find the restaurant by the key
      .child('votes') // get the number of votes
  }
  handleDeselect() {}
  
  render () {
    const { restaurants } = this.props
    return (
      <section className="Restaurants">
      // **** 1 pass down a handler for voting.  ↴
      // prettifies the code a bit...
        { 
          map(restaurants, (restaurant, key) => {
            <Restaurant 
              key={key} 
              {...restaurant} 
              />
          })
        }
      
      </section>
    );
  }
}

Restaurants.propTypes = {
  user: PropTypes,
  restaurantsRef: PropTypes.object,
  restaurants: PropTypes.object
};

export default Restaurants;

Now, we also need to find out who the user is, in order to cast a vote on his behalf.

  //	****		Application.js		****

...
render() {
    const { currentUser, restaurants } = this.state
    
    return (
      <div className="Application">
        <header className="Application--header">
          <h1>Lunch Rush</h1>
        </header>
        <div>
          {!currentUser && <SignIn />}
          {currentUser && 
          <div>
            <NewRestaurant />
            // **** 1 pass the user.  ↴
            <Restaurants restaurants={restaurants} user={currentUser} />
            <CurrentUser user={currentUser} />
          </div>
          } 
        </div>
      </div>
    );
  }

Then use this:

  //	****		Restaurants.js		****

import React, { Component, PropTypes } from 'react';
import Restaurant from './Restaurant';
import map from 'lodash/map';
import { database } from './firebase'
import './Restaurants.css';

class Restaurants extends Component {
  constructor(props) {
    super(props);
  }
  
  handleSelect(key) {
    // **** 1. get the `user`  ↴
    const currentUser = this.props.user
    database.ref('/restaurants')
      .child(key)
      .child('votes')
      // **** 2.  ↴
      .child(currentUser.uid) // use the uid in case they have the same name
      .set(currentUser.displayName)
  }
  
  handleDeselect() {
    // **** 3 deselect is almost the same.  ↴
    const currentUser = this.props.user
    database.ref('/restaurants')
      .child(key)
      .child('votes')
      .child(currentUser.uid)
      // **** 4 ++-- use remove the unvote.  ↴
      .remove(currentUser.displayName)
  }
  
  render () {
    const { restaurants } = this.props
    return (
      <section className="Restaurants">
        { 
          map(restaurants, (restaurant, key) => {
            <Restaurant 
              key={key} 
              {...restaurant} 
              // **** 5 finally pass the vote handlers.  ↴
              handleSelect= { () => this.handleSelect(key)}
              handleDeselect= { () => this.handleDeselect(key)}
              />
          })
        }
      
      </section>
    );
  }
}

Restaurants.propTypes = {
  user: PropTypes,
  restaurantsRef: PropTypes.object,
  restaurants: PropTypes.object
};

export default Restaurants;

Now, we just need to wire up the button:

  //	****		Restaurant.js		****

import React, { Component, PropTypes } from 'react';
import map from 'lodash/map';
import './Restaurant.css';

class Restaurant extends Component {
  render () {
    // **** 2 ++-- grab the handlers too.  ↴
    const { name, handleSelect } = this.props
    return (
      <article className="Restaurant">
        <h3> { name } </h3>
        // **** 1 add the handlers.  ↴
        <button onClick={handleSelect}>
        // at this point voting now works
          Yea, I'd go there
        </button>
      </article>
    );
  }
}

Restaurant.propTypes = {
  name: PropTypes.string,
  votes: PropTypes.object,
  user: PropTypes.object,
  handleSelect: PropTypes.func,
  handleDeselect: PropTypes.func
};

export default Restaurant;

Now, users can vote for a restaurant!

Displaying voters and unvoting

Unvoting

  //	****		Restaurant.js		****

import React, { Component, PropTypes } from 'react';
import map from 'lodash/map';
import './Restaurant.css';

class Restaurant extends Component {
  render () {
    const { name, handleSelect, handleDeselect } = this.props
    return (
      <article className="Restaurant">
        <h3> { name } </h3>
        <button onClick={handleSelect}>
          Yea, I'd go there
        </button>
        // **** 1 unvoting.  ↴
        <button className="destructive" onClick={handleDeselect}>
          Nah, nevermind
        </button>
      </article>
    );
  }
}

Restaurant.propTypes = {
  name: PropTypes.string,
  votes: PropTypes.object,
  user: PropTypes.object,
  handleSelect: PropTypes.func,
  handleDeselect: PropTypes.func
};

export default Restaurant;

Showing the users

  //	****		Restaurant.js		****

import React, { Component, PropTypes } from 'react';
import map from 'lodash/map';
import './Restaurant.css';

class Restaurant extends Component {
  render () {
    // **** 1 ++-- add `votes` thanks to the spread opreator from before.  ↴
    const { name, votes handleSelect, handleDeselect } = this.props
    return (
      <article className="Restaurant">
        <h3> { name } </h3>
        // **** 2 list the voters.  ↴
        <ul>
          // conditionallu
          { votes &&  map(votes, (vote, key) => <li key=key>{ vote }</li>)}
        </ul>
        <button onClick={handleSelect}>
          Yea, I'd go there
        </button>
        // **** 1 unvoting.  ↴
        <button className="destructive" onClick={handleDeselect}>
          Nah, nevermind
        </button>
      </article>
    );
  }
}

Restaurant.propTypes = {
  name: PropTypes.string,
  votes: PropTypes.object,
  user: PropTypes.object,
  handleSelect: PropTypes.func,
  handleDeselect: PropTypes.func
};

export default Restaurant;