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.
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:
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:
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:
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">© 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:
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">© 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.
Adding todos doesn’t yet show up in the UI, but you can browse your Firebase database to see any todos that you add!
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">© 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:
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!
Discussion