Using React, Firebase, and Ant Design to Quickly Prototype Web Applications

In this guide I will show you how to use Firebase, React, and Ant Design as building blocks to build functional, high-fidelity web applications. To illustrate this, we’ll go through an example of building a todo list app.

These days, there are so many tools available for web development that it can feel paralyzing. Which server should you use? What front-end framework are you going to choose? Usually, the recommended approach is to use the technologies that you know best. Generally, this means choosing a battle-tested database like PostgreSQL or MySQL, choosing a MVC framework for your webserver (my favourite is Adonis), and either using that framework’s rendering engine or using a client-side javascript library like ReactJS or AngularJS.

Using the above approach is productive – especially if you have good boilerplate to get you started – but what if you want to build something quickly with nearly zero setup time? Sometimes a mockup doesn’t convey enough information to a client; sometimes you want to build out an MVP super fast for a new product.

The source code for this example is available here. If you’re looking for a good IDE to use during this guide, I highly recommend Visual Studio Code.

React + Firebase + Ant Design = Love

A React Development Environment Using Create React App

React is a javascript library for building user interfaces. The library is “component based” meaning you can create building blocks and compose your interface out these reusable components. Create React App, on the other hand, is a zero-configuration React environment. It works out of the box with one shell command and keeps your environment up to date.

To get started, install Node.js for your system by following the instructions here.

Then start your new Create React App project:

npx create-react-app quick-todo && cd quick-todo

Now, you can run the development webserver with:

npm start

Visit http://localhost:3000/ in your browser and you should see this:

Create React App starter page

Great! You now have a functional React development environment.

Integrate Firebase with Your Application

Now that you have a React development environment, the next step is to integrate Firebase into your app. Firebase’s core product is a real-time database service. This means that your users do not need to refresh a page to see updates to the state of the application and it takes no extra effort on your part to make this happen.

Head over https://firebase.google.com and create an account if you haven’t already. Then create a new Firebase project called quick-todo.

Once you have your Firebase project, provision a “Cloud Firestore” database:

Provision Cloud Firestore Database Step 1

Provision Cloud Firestore Database Step 2

Provision Cloud Firestore Database Step 3

Here we’re using no authentication on the database because we’re building a prototype. When you build a real application, you’ll want to create proper security rules but let’s not worry about that for now.

Ok, now that your Firebase database is provisioned, let’s get it integrated into your React app. In your project directory, run the following command to install the necessary dependencies:

npm i --save firebase @firebase/app @firebase/firestore

Then, in your project, add a new file in the src directory called firestore.js with the following contents:

firestore.js

import firebase from "@firebase/app";
import "@firebase/firestore";

const config = {
  apiKey: "<apiKey>",
  authDomain: "<authDomain>",
  databaseURL: "<databaseURL>",
  projectId: "<projectId>",
  storageBucket: "",
  messagingSenderId: "<messageingSenderId>"
};

const app = firebase.initializeApp(config);
const firestore = firebase.firestore(app);

export default firestore;

Make sure you insert the apiKey and other parameters from your own project. You can find these in your project’s settings:

Firebase Settings

Ok! Now we have access to a real-time Firebase database anywhere in the app by importing our firestore.js utility:

import firestore from "./firestore";

Install Ant Design

Ant Design is a comprehensive design system that includes a full suite of React components. Because React is component-based, it’s fairly simple to use Ant Design’s React components as building blocks to quickly put together a prototype.

To start using Ant Design’s React component system, install antd:

npm i --save antd

Pulling It All Together

We now have all all the tools we need to build our prototype. Let’s use our environment to build a high-fidelity prototype of a todo app.

First, let’s clean our slate. Modify App.js and App.css so that they look like this:

App.js

import React, { Component } from "react";

import "./App.css";

class App extends Component {
  render() {
    return <div className="App" />;
  }
}

export default App;

App.cs

@import "~antd/dist/antd.css";

.App {
  text-align: center;
}

Notice how we’ve imported the css for antd.

Now, let’s setup some basic structure for our todo app. We can use the antd Layout component for this:

App.js

import React, { Component } from "react";
import { Layout } from "antd";

import "./App.css";

const { Header, Footer, Content } = Layout;

class App extends Component {
  render() {
    return (
      <Layout className="App">
        <Header className="App-header">
          <h1>Quick Todo</h1>
        </Header>
        <Content className="App-content">Content</Content>
        <Footer className="App-footer">&copy; My Company</Footer>
      </Layout>
    );
  }
}

export default App;

App.css

@import "~antd/dist/antd.css";

.App {
  text-align: center;
}

.App-header h1 {
  color: whitesmoke;
}

.App-content {
  padding-top: 100px;
  padding-bottom: 100px;
}

With these changes made, we can run our development server. You should seed something like this:

Quick Todo Step 1

Now, we can utilize our firestore.js module that we create earlier to start adding todos to our real-time firebase database. You can read more about how to use Firebase Cloud Firestore here.

Let’s walk through the following changes to our source code:

App.js

import React, { Component } from "react";
import { Layout, Input, Button } from "antd";

// We import our firestore module
import firestore from "./firestore";

import "./App.css";

const { Header, Footer, Content } = Layout;

class App extends Component {
  constructor(props) {
    super(props);
    // Set the default state of our application
    this.state = { addingTodo: false, pendingTodo: "" };
    // We want event handlers to share this context
    this.addTodo = this.addTodo.bind(this);
  }

  async addTodo(evt) {
    // Set a flag to indicate loading
    this.setState({ addingTodo: true });
    // Add a new todo from the value of the input
    await firestore.collection("todos").add({
      content: this.state.pendingTodo,
      completed: false
    });
    // Remove the loading flag and clear the input
    this.setState({ addingTodo: false, pendingTodo: "" });
  }

  render() {
    return (
      <Layout className="App">
        <Header className="App-header">
          <h1>Quick Todo</h1>
        </Header>
        <Content className="App-content">
          <Input
            ref="add-todo-input"
            className="App-add-todo-input"
            size="large"
            placeholder="What needs to be done?"
            disabled={this.state.addingTodo}
            onChange={evt => this.setState({ pendingTodo: evt.target.value })}
            value={this.state.pendingTodo}
            onPressEnter={this.addTodo}
          />
          <Button
            className="App-add-todo-button"
            size="large"
            type="primary"
            onClick={this.addTodo}
            loading={this.state.addingTodo}
          >
            Add Todo
          </Button>
        </Content>
        <Footer className="App-footer">&copy; My Company</Footer>
      </Layout>
    );
  }
}

export default App;

App.css

@import "~antd/dist/antd.css";

.App {
  text-align: center;
}

.App-header h1 {
  color: whitesmoke;
}

.App-content {
  padding-top: 100px;
  padding-bottom: 100px;
}

.App-add-todo-input {
  max-width: 300px;
  margin-right: 5px;
}

.App-add-todo-button {
}

With these changes, you can see that we now have an input on our application to add new todos.

Quick Todo Step 2

Adding todos doesn’t yet show up in the UI, but you can browse your Firebase database to see any todos that you add!

Quick Todo Step 3

The last step to having a fully functional todo app is to show the list of todos and allow the user to complete them. To do this, we can use the List component from Ant Design to show incomplete todos. Take the following source code for example:

App.js

import React, { Component } from "react";
import { Layout, Input, Button, List, Icon } from "antd";

// We import our firestore module
import firestore from "./firestore";

import "./App.css";

const { Header, Footer, Content } = Layout;

class App extends Component {
  constructor(props) {
    super(props);
    // Set the default state of our application
    this.state = { addingTodo: false, pendingTodo: "", todos: [] };
    // We want event handlers to share this context
    this.addTodo = this.addTodo.bind(this);
    this.completeTodo = this.completeTodo.bind(this);
    // We listen for live changes to our todos collection in Firebase
    firestore.collection("todos").onSnapshot(snapshot => {
      let todos = [];
      snapshot.forEach(doc => {
        const todo = doc.data();
        todo.id = doc.id;
        if (!todo.completed) todos.push(todo);
      });
      // Sort our todos based on time added
      todos.sort(function(a, b) {
        return (
          new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
        );
      });
      // Anytime the state of our database changes, we update state
      this.setState({ todos });
    });
  }

  async completeTodo(id) {
    // Mark the todo as completed
    await firestore
      .collection("todos")
      .doc(id)
      .set({
        completed: true
      });
  }

  async addTodo() {
    if (!this.state.pendingTodo) return;
    // Set a flag to indicate loading
    this.setState({ addingTodo: true });
    // Add a new todo from the value of the input
    await firestore.collection("todos").add({
      content: this.state.pendingTodo,
      completed: false,
      createdAt: new Date().toISOString()
    });
    // Remove the loading flag and clear the input
    this.setState({ addingTodo: false, pendingTodo: "" });
  }

  render() {
    return (
      <Layout className="App">
        <Header className="App-header">
          <h1>Quick Todo</h1>
        </Header>
        <Content className="App-content">
          <Input
            ref="add-todo-input"
            className="App-add-todo-input"
            size="large"
            placeholder="What needs to be done?"
            disabled={this.state.addingTodo}
            onChange={evt => this.setState({ pendingTodo: evt.target.value })}
            value={this.state.pendingTodo}
            onPressEnter={this.addTodo}
            required
          />
          <Button
            className="App-add-todo-button"
            size="large"
            type="primary"
            onClick={this.addTodo}
            loading={this.state.addingTodo}
          >
            Add Todo
          </Button>
          <List
            className="App-todos"
            size="large"
            bordered
            dataSource={this.state.todos}
            renderItem={todo => (
              <List.Item>
                {todo.content}
                <Icon
                  onClick={evt => this.completeTodo(todo.id)}
                  className="App-todo-complete"
                  type="check"
                />
              </List.Item>
            )}
          />
        </Content>
        <Footer className="App-footer">&copy; My Company</Footer>
      </Layout>
    );
  }
}

export default App;

App.css

@import "~antd/dist/antd.css";

.App {
  text-align: center;
}

.App-header h1 {
  color: whitesmoke;
}

.App-content {
  padding-top: 100px;
  padding-bottom: 100px;
}

.App-add-todo-input {
  max-width: 300px;
  margin-right: 5px;
}

.App-add-todo-button {
}

.App-todos {
  background-color: white;
  max-width: 400px;
  margin: 0 auto;
  margin-top: 20px;
  margin-bottom: 20px;
}

.App-todo {
  /* position: relative; */
}

.App-todo-complete {
  font-size: 22px;
  font-weight: bold;
  cursor: pointer;
  position: absolute;
  right: 24px;
}

With these final changes, we can see the todos that are added in our application as a list:

Quick Todo Step 4

And there we have it! Using React, Firebase, and Ant Design, we were able to quickly create a high-fidelity web application. Using these tools can help you create something functional and aesthetically pleasing in no time.

This can be very valuable when you need to demonstrate functionality of an app to someone without spending too much time building it.


This guide focuses on using tools to quickly build prototypes but I think this approach can also be used to create production-ready web apps. Ant Design can be themed and Firebase is extremely scalable.

The only question of using Firebase over a traditional webserver is cost. For applications with many users, Firebase may get expensive quickly; however, using the traditional approach of webserver and database can also be costly to host. Additionally, you also need to take into account the time and cost of building, configuring, and managing your webserver and database!

If you would like publish your new web application for others to see, you can follow my guide on deploying firebase applications.

Comments