Displaying Content from Firebase
Now, we want to display the list of restaurants on the screen. We want to subscribe to the restaurants database and update the app state. In React, the common pattern is to push your data to the "top" and pass it down. This can become tricky as the application grows. He mentions Redux as a solution to this.
We want to subscribe to the /restaurants
ref in the database.
// **** Application.js ****
import React, { Component } from 'react';
import { auth, database } from './firebase';
import CurrentUser from './CurrentUser';
import SignIn from './SignIn';
import NewRestaurant from './NewRestaurant';
import Restaurants from './Restaurants';
import './Application.css';
// **** 3 use lodash for object iteration. ↴
import map from 'loadash/map'
class Application extends Component {
constructor(props) {
super(props);
this.state = {
currentUser: null,
// **** 5. ↴
restaurants: null
}
// **** 1. get a reference to the database ↴
this.restaurantRef = database.ref('/restaurants')
}
componentDidMount() {
auth.onAuthStateChanged((currentUser) => {
this.setState({ currentUser })
// **** 2 only listen to the db once the user has logged in. ↴
this.restaurantRef.on('value', snapshot => {
// we need to use the snapshot of the database as there can be many mutations happening the same time
console.log(snapshot.val()) // first just log out
// **** 4. ↴
this.setState({ restaurants: snapshot.val() })
// at this point the react state updates on load
})
})
}
render() {
// **** 6 grab also `restaurants`. ↴
const { currentUser, restaurants } = this.state
return (
<div className="Application">
<header className="Application--header">
<h1>Lunch Rush</h1>
</header>
<div>
{!currentUser && <SignIn />}
{currentUser &&
<div>
<NewRestaurant />
// **** 7. map with lodash ↴
{ map(restaurants, (restaurant, key) => <p key={key}>{ restaurant.name }</p>)} // `key`: so that React knows which smallest node to change in the DOM
<CurrentUser user={currentUser} />
// now the circle is closed, adding and displaying a new restaurant through Firebase is working
</div>
}
</div>
</div>
);
}
}
export default Application;
Note that above, Firebase is not using arrays for the restaurants, but object:key pairs. So we need to iterate over it like an array. It's a bit weird to do this with JavaScript (own properties, ES6 tricks...). Suggests lodash to do this.
Adding voting
// **** Application.js ****
import React, { Component } from 'react';
import { auth, database } from './firebase';
import CurrentUser from './CurrentUser';
import SignIn from './SignIn';
import NewRestaurant from './NewRestaurant';
import Restaurants from './Restaurants';
import './Application.css';
import map from 'loadash/map'
class Application extends Component {
constructor(props) {
super(props);
this.state = {
currentUser: null,
restaurants: null
}
this.restaurantRef = database.ref('/restaurants')
}
componentDidMount() {
auth.onAuthStateChanged((currentUser) => {
this.setState({ currentUser })
this.restaurantRef.on('value', snapshot => {
this.setState({ restaurants: snapshot.val() })
})
})
}
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 restaurants data to the component. ↴
// { map(restaurants, (restaurant, key) => <p key={key}>{ restaurant.name }</p>)}
<Restaurants restaurants={restaurants} />
<CurrentUser user={currentUser} />
</div>
}
</div>
</div>
);
}
}
export default Application;
Moving on to the Restaurants.js
import React, { Component, PropTypes } from 'react';
import Restaurant from './Restaurant';
import map from 'lodash/map';
import './Restaurants.css';
class Restaurants extends Component {
constructor(props) {
super(props);
}
render () {
// **** 1. ↴
const { restaurants } = this.props
return (
<section className="Restaurants">
// **** 2. map over the restaurants here (push downwards the data) ↴
// since the `restaurant` data _will_ grow over time
// from just having the `name` property, will use the `...` spread operator
// this will take each key of the object and pass them individually
{ map(restaurants, (restaurant, key) => <Restaurant key={key} {...restaurant} />))
</section>
);
}
}
Restaurants.propTypes = {
user: PropTypes,
restaurantsRef: PropTypes.object,
restaurants: PropTypes.object
};
export default Restaurants;
Now, continue with displaying a single restaurant (subcomponent)
// **** Restaurant.js ****
import React, { Component, PropTypes } from 'react';
import map from 'lodash/map';
import './Restaurant.css';
class Restaurant extends Component {
render () {
// **** 1. ↴
const { name } = this.props
return (
<article className="Restaurant">
// **** 2. ↴
<h3> { name } </h3>
</article>
);
}
}
Restaurant.propTypes = {
name: PropTypes.string,
votes: PropTypes.object,
user: PropTypes.object,
handleSelect: PropTypes.func,
handleDeselect: PropTypes.func
};
export default Restaurant;