Voting Functionality
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;