Intro to Firebase and React

Let's take a look at building something using Firebase and React. We'll be building something called Fun Food Friends, a web application for planning your next potluck, which hopefully feels like something rather "real world", in that you can imagine using these technologies in your own production projects. The big idea in this app is that you and your friends will be able to log in and be able to see and post information about what you're planning to bring to the potlock.

Article Series:

  1. Intro to Firebase and React (You are here!)
  2. User Authentication

When we're finished, it will look like this:

Our example app: Fun Food Friends

This article assumes you already have some basic knowledge of how React works and maybe built a few small apps with React. If you haven't, I would recommend checking out a series like Wes Bos' React for Beginners first before continuing on.

What is Firebase?

Google's Firebase is a cloud-based database hosting service that will set up a database for you and host it, as well as offer you the tools to interact with it. You can use it to store and retrieve data in real time. That's not all Firebase does, it can do more things like handle user authentication and store files, but we'll be mainly focusing on data storage.

The data storage ability of Firebase make it a perfect fit for React. A persistent, real-time backend for your application to plug in to!

How does Firebase store data?

Firebase stores data as a giant object with key-value pairs. Unlike JSON or JavaScript objects, there are no arrays in Firebase.

A Firebase database might look something like this:

{
      "groceries": {
        "-KjQTqG3R2dPT8s2jylW": "tomato",
        "-KjQTrds1feHT3GH_29o": "pasta",
        "-KjQTsmfBR8zN1SwPPT8": "milk",
        "-KjQTtnzt_jJZPoCHWUM": "sugar"
      },
      "users": {
        "name": {
          "-KjQTyIfKFEVMYJRZ09X": "simon",
          "-KjQU-Xuy5s7I-On9rYP": "ryan",
          "-KjQU0MYVeKRsLuIQCYX": "sylvia"
        }
      }
}

For more information on the nuances of structuring data in Firebase, you can read the amazing Firebase documentation.

Ready to start? Let's dig in!

Getting Started: Setting up Our App

We'll start by using the incredibly handy `create-react-app` package in order to quickly set up a new React project without having to worry about any build configuration. Open up your command line, and type the following:

npm install -g create-react-app
    
create-react-app fun-food-friends
cd fun-food-friends
yarn add firebase --dev
yarn start

This will boot up your app in the browser, and start a watch task in your terminal so that we can begin hacking away at the project. We're also installing the `firebase` package here as we'll need it for the next step.

Creating our Firebase Database

Now that our app is set up, we'll need to create an account and database on Firebase so that we can link up our application to it.

Head on over to Firebase's website, and click Get Started.

This will take you to a page where you’ll be asked to authenticate with your Google account. Select the account that you’d like this project to be affiliated with, and press OK.

This should take you to the Firebase console, which looks something like this:

Now let's create our project's database. Click Add Project. Let's call it "fun-food-friends" and press OK.

This will take you to your app's dashboard, which looks like this:

Since we'll be building a web app, select Add Firebase to your web app. This will trigger a popup with some code that looks like this:

<script src="https://www.gstatic.com/firebasejs/3.9.0/firebase.js"></script>
<script>
  // Initialize Firebase
  var config = {
     apiKey: "AIzaSyDblTESEB1SbAVkpy2q39DI2OHphL2-Jxw",
    authDomain: "fun-food-friends-eeec7.firebaseapp.com",
    databaseURL: "https://fun-food-friends-eeec7.firebaseio.com",
    projectId: "fun-food-friends-eeec7",
    storageBucket: "fun-food-friends-eeec7.appspot.com",
    messagingSenderId: "144750278413"
  };
  firebase.initializeApp(config);
</script>

Since we'll be importing Firebase into our project using ES6 modules, we won't need those script tags. That config object is important though: it's how we authenticate our React application with our Firebase database.

Hooking up our App to Firebase

Copy that whole config object, and head back over to your React project. Find your `src` folder, and create a file called `firebase.js`. Inside of it, let's import firebase, our config, and initialize our app:

// src/firebase.js
import firebase from 'firebase'
const config = {
    apiKey: "AIzaSyDblTESEB1SbAVkpy2q39DI2OHphL2-Jxw",
    authDomain: "fun-food-friends-eeec7.firebaseapp.com",
    databaseURL: "https://fun-food-friends-eeec7.firebaseio.com",
    projectId: "fun-food-friends-eeec7",
    storageBucket: "fun-food-friends-eeec7.appspot.com",
    messagingSenderId: "144750278413"
};
firebase.initializeApp(config);
export default firebase;

One last thing we'll need to do before we can dive into roughing out our App. We need to temporarily disable authentication requirements on our app so that we can add and remove items without needing to have any kind of user authentication flow.

From the Firebase Dashboard, on the left-hand side of the screen, you'll notice that there is a Database tab. Click on it. Then, on the right-hand side, under the subheading Realtime Database, you'll see a Rules tab. This will cause an object to appear that looks something like this:

{
    "rules": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
}

We need to set .read and .write to both be equal to true, otherwise later, when we try to add data to our database from our application, Firebase won't let us. When you're finished, it should look something like this:

Make sure to click the Publish button.

And that's all there is to hooking up our database! Anytime we need a component of our application to connect with our Firebase database, we simply need to import our firebase module and we'll have direct reference to it.

Building out our App's Rough Skeleton

Let's build out a rough HTML skeleton for our application. We'll build a simple form with two inputs:

  1. A field where the user can submit their name
  2. A field where the user can enter what food they're bringing to the potluck.

Since our app is quite simple, we'll keep everything inside of one main component, `App.js`. Open up `src/App.js`, and remove the `App` component, replacing it with this basic skeleton:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className='app'>
        <header>
            <div className='wrapper'>
              <h1>Fun Food Friends</h1>
              
            </div>
        </header>
        <div className='container'>
          <section className='add-item'>
              <form>
                <input type="text" name="username" placeholder="What's your name?" />
                <input type="text" name="currentItem" placeholder="What are you bringing?" />
                <button>Add Item</button>
              </form>
          </section>
          <section className='display-item'>
            <div className='wrapper'>
              <ul>
              </ul>
            </div>
          </section>
        </div>
      </div>
    );
  }
}
export default App;

Get the CSS

I've prepared a little bit of CSS for you to paste into the `App.css` file, just so that our app doesn't look totally bland. If you want to grab it, just go here and copy and paste the raw contents you find there into your `src/App.css` file!

We'll also need to embed a link to Google Fonts and Font Awesome, so go ahead and open up `public/index.html` and add the following lines below the favicon:

<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">

<!-- add the lines below -->

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">  

At this point, your app should look like this:

Connecting our Form to Component State

Before we can start adding data into our Firebase database, we need to connect our inputs to our component’s state, so that React can keep track of them.

First, let's carve out some space in our component's state - a space to keep track of the user using our app (username) and the item they intend to bring (currentItem). We'll do this by creating a constructor() hook for our app and setting a default value for our input's state there:

class App extends Component {
  constructor() {
    super();
    this.state = {
      currentItem: '',
      username: ''
    }
  }
  // ....

We'll add a onChange event handlers to our inputs, as well as providing them with a value derived from our state (this is called a "controlled input"), like this:

<section className="add-item">
  <form>
    <input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
    <input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
    <button>Add Item</button>
  </form>
</section>

And finally, we'll create a catch-all handleChange method that receives the event from our inputs, and updates that input's corresponding piece of state:

handleChange(e) {
  this.setState({
    [e.target.name]: e.target.value
  });
}

If you aren't familiar with using brackets to dynamically determine key name in an object literal, check out the MDN docs on computed properties.

Since we're using ES6 classes and need access to this in our handleChange method, we'll also need to bind it back in our constructor() component like this:

constructor() {
  super();
  this.state = {
    username: '',
    currentItem: ''
  }
  this.handleChange = this.handleChange.bind(this);
}

If you now use the React DevTools to inspect your App component's state, you'll see that both of your inputs are now successfully hooked up and being tracked in your component's state:

Adding a new Potluck Item to your Database

Now that we're tracking our inputs, let's make it so that we can add a new item to our database so that Firebase can keep track of it.

First we'll need to connect to Firebase in order to do this, we'll start by importing our firebase module that we created earlier. We'll also delete the logo.svg import, since it's just an unneeded part of the create-react-app boiler plate and will cause warnings if we don't:

import React, { Component } from 'react';
import logo from './logo.svg'; // <--- remove this line
import './App.css';
import firebase from './firebase.js'; // <--- add this line

Once that's done, we'll need to make our 'Add Item' button let Firebase know what we'd like to add to our database and where we'd like to put it.

First we'll attach a submit event listener for our form, and have it call a handleSubmit method we'll write in a minute:

<form onSubmit={this.handleSubmit}>
  <input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
  <input type="text" name="currentItem" placeholder="What are you bringing ?" onChange={this.handleChange} value={this.state.currentItem} />
  <button>Add Item</button>
</form>

Don't forget to bind it in the constructor!

constructor() {
  super();
  this.state = {
    currentItem: '',
    username: ''
  }
  this.handleChange = this.handleChange.bind(this);
  this.handleSubmit = this.handleSubmit.bind(this); // <-- add this line
}

And now add the handleSubmit method to your component:

handleSubmit(e) {
  e.preventDefault();
  const itemsRef = firebase.database().ref('items');
  const item = {
    title: this.state.currentItem,
    user: this.state.username
  }
  itemsRef.push(item);
  this.setState({
    currentItem: '',
    username: ''
  });
}

Let's break down what's going here:

  • e.preventDefault() - we need to prevent the default behavior of the form, which if we don't will cause the page to refresh when you hit the submit button.
  • const itemsRef = firebase.database().ref('items'); - we need to carve out a space in our Firebase database where we'd like to store all of the items that people are bringing to the potluck. We do this by calling the ref method and passing in the destination we'd like them to be stored (items).
  • const item = { /* .. */ } here we grab the item the user typed in (as well as their username) from the state, and package it into an object so we ship it off to our Firebase database.
  • itemsRef.push(item) similar to the Array.push method, this sends a copy of our object so that it can be stored in Firebase.
  • Finally this.setState({ currentItem: '', username: '' }); is just so that we can clear out the inputs so that an additional item can be added.

Now try adding a new item, and hitting submit! If you don't have any errors in your console, you should be able to head on over to the Firebase dashboard, where you'll see something like this inside your Database tab:

If you click the little + next to items you'll be able to look inside, like this:

That strange looking -Kk8lHSMqC5oP6Qai0Vx key you see is a programmatically generated key created by Firebase when we called the push method, but inside you'll find whatever item you added to the Potluck.

You'll notice that all of our records are stored as objects with properties that have the generated names you see above - just another quick reminder that there are no arrays in Firebase!

Try adding more items and see what happens.

Way to go! We're almost there, but we still have one more step: getting our potluck items to appear on the page.

Retrieving our Potluck Items from the database

Just like in a traditional React app, we need to find some way to keep track of all of the potluck dishes so that we can display what people are planning to bring on to the page.

Without a database, this poses an issue, since every time we refresh the page any new dishes that were added to the potluck would get lost. But with Firebase, this is a snap to fix!

First, let's create a variable called items inside of default state. This will eventually hold all of the potluck items that are currently being tracked inside of our Firebase database.

constructor() {
  super();
  this.state = {
    currentItem: '',
    username: '',
    items: []
  }
  this.handleChange = this.handleChange.bind(this);
  this.handleSubmit = this.handleSubmit.bind(this);
}

Next, we need to actually grab those items from our Firebase database so that we can store them into our state.

The Firebase API offers us an incredibly easy way to not only grab this kind information from our database, but also to update us when new values get added to our database. It accomplishes this using the value custom event listener.

It looks like this:

itemsRef.on('value', (snapshot) => {
  console.log(snapshot.val());
});

The callback here, which we've called snapshot, provides you with a bird's eye overview of the items ref inside of your database. From here, you can easily grab a list of all of the properties inside of that items ref, using the .val() method which you can call on the snapshot.

This value automatically fires on two occassions:

  1. Any time a new item is added or removed from our items reference inside of our database
  2. The first time the event listener is attached

This makes it especially useful for initially grabbing a list of all of the items inside of our database, and then subsequently tracking when new items get added and removed.

We'll attach this event listener inside of our componentDidMount, so that we start tracking our Potluck items as soon as our component loads on to the page:

componentDidMount() {
  const itemsRef = firebase.database().ref('items');
  itemsRef.on('value', (snapshot) => {
    let items = snapshot.val();
    let newState = [];
    for (let item in items) {
      newState.push({
        id: item,
        title: items[item].title,
        user: items[item].user
      });
    }
    this.setState({
      items: newState
    });
  });
}

Here, we instantiate a new array and populate it with the results that come back from our value listener. We for…in over each key, and push the result into an object inside our newState array. Finally, once all the keys are iterated over (and therefore all items are grabbed from our database), we update the state with this list of items from our database.

Inspect your App using the React Dev Tools - you'll notice that you now have an items property inside of your state with all of the items people have submitted for your potluck!

Displaying Potluck Items on the Page

Now let's get these potluck items to actually display on the page. This is relatively easy, now that we have a list of all of our items being grabbed from Firebase and stored inside of our state. We just map over it and print the results on to the page, like so:

<section className='display-item'>
  <div className="wrapper">
    <ul>
      {this.state.items.map((item) => {
        return (
          <li key={item.id}>
            <h3>{item.title}</h3>
            <p>brought by: {item.user}</p>
          </li>
        )
      })}
    </ul>
  </div>
</section>

Try adding a new item through your form. You'll notice that it automatically causes a new list item to appear on the page!

It's not magic, Firebase's value event is firing when you push the new item into your database, and sending back a new snapshot with a list of all of the items currently in your database, which ultimate updates your component through a setState which triggers a re-render and displays the new item on the page.

But we digress. There's still one more step! We need to make it so that we can remove an item from the page.

Removing Items from the Page

We'll need to create a new method on our component for this: removeItem. This method will need to be passed that unique key which serves as the identifier for each one of the items inside of our Firebase database.

It's very simple, and looks like this:

removeItem(itemId) {
  const itemRef = firebase.database().ref(`/items/${itemId}`);
  itemRef.remove();
}

Here, instead of grabbing all of the items as we did before when adding a new item, we instead look up a specific item by its key (that strange -Kk8lHSMqC5oP6Qai0Vx key from before). We can then call firebase.database()'s remove method, which strips it from the page.

Finally, we'll need to add a button to our UI with an onClick that calls our removeItem method and passes it the item's key, like follows:

{this.state.items.map((item) => {
    return (
      <li key={item.id}>
        <h3>{item.title}</h3>
        <p>brought by: {item.user}</p>
        <button onClick={() => this.removeItem(item.id)}>Remove Item</button>
      </li>
    )
  })
}

And that's all there is to it! Just like our addItem method, our UI and component state automatically update when an item is removed from the database.

Here's what our completed `App.js` should look like:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import firebase from './firebase.js';

class App extends Component {
  constructor() {
    super();
    this.state = {
      currentItem: '',
      username: '',
      items: []
    }
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleChange(e) {
    this.setState({
      [e.target.name]: e.target.value
    });
  }
  handleSubmit(e) {
    e.preventDefault();
    const itemsRef = firebase.database().ref('items');
    const item = {
      title: this.state.currentItem,
      user: this.state.username
    }
    itemsRef.push(item);
    this.setState({
      currentItem: '',
      username: ''
    });
  }
  componentDidMount() {
    const itemsRef = firebase.database().ref('items');
    itemsRef.on('value', (snapshot) => {
      let items = snapshot.val();
      let newState = [];
      for (let item in items) {
        newState.push({
          id: item,
          title: items[item].title,
          user: items[item].user
        });
      }
      this.setState({
        items: newState
      });
    });
  }
  removeItem(itemId) {
    const itemRef = firebase.database().ref(`/items/${itemId}`);
    itemRef.remove();
  }
  render() {
    return (
      <div className='app'>
        <header>
            <div className="wrapper">
              <h1>Fun Food Friends</h1>
                             
            </div>
        </header>
        <div className='container'>
          <section className='add-item'>
                <form onSubmit={this.handleSubmit}>
                  <input type="text" name="username" placeholder="What's your name?" onChange={this.handleChange} value={this.state.username} />
                  <input type="text" name="currentItem" placeholder="What are you bringing?" onChange={this.handleChange} value={this.state.currentItem} />
                  <button>Add Item</button>
                </form>
          </section>
          <section className='display-item'>
              <div className="wrapper">
                <ul>
                  {this.state.items.map((item) => {
                    return (
                      <li key={item.id}>
                        <h3>{item.title}</h3>
                        <p>brought by: {item.user}
                          <button onClick={() => this.removeItem(item.id)}>Remove Item</button>
                        </p>
                      </li>
                    )
                  })}
                </ul>
              </div>
          </section>
        </div>
      </div>
    );
  }
}
export default App;

Conclusion

Now you can truly see how Firebase and React play beautifully together. Firebase's ability to persist data on the fly, coupled with React's component life cycle, makes for an incredibly simple and powerful way to quickly build up simple applications.

This article just scratches the surface of what the Firebase API can provide us. For example, with just a few more steps (and perhaps we will go over this in a future article), it would be incredibly easy to expand this application so that users could log in and out, be able to have a display photo next to the item that they are bringing, and only be able to remove their own items.

Happy Firebasing!

Article Series:

  1. Intro to Firebase and React (You are here!)
  2. User Authentication