Create Todo App: Real-time, ReactJS, TypeScript, Firebase in 30 minutes

13
minutes
Mis à jour le
26/1/2022

Share this post

Learn how to build and deploy a real-time todo list application using React, Typescript and Firebase. Go further with automatic Continuous Deployment.

#
Firebase
#
React
#
Typescript
Erine Dupont
Tech Lead

Every developer must have gone through this: how to build a web application from scratch? How to start a project? Which technology to adopt, for which need?

There’s nothing like training to progress, and that’s why I want to share with you my experience to build a very simple web application using ReactJS and Firebase.

As prerequisites, you will need to know the basics of Typescript and noSQL databases.


Why ReactJS / Firebase

React is a well-known front-end JavaScript library, free and easy-to-use. Thanks to create-react-app, you can set up a whole project in a minute! There is also a lot of documentation if you encounter any trouble (which won't happen if you follow this tutorial).

Firebase is a Google-powered tool with a free plan allowing you many things, especially using real-time database to retrieve and update your data. Furthermore, its documentation is also very complete, and React has a deployment tutorial with Firebase.

Now, let’s do this!

Set-up React app

To create the very-basis of your app, you only need one line of code in your terminal :

npx create-react-app my-app

This command will create a whole structure of code for an app named my-app, that you can access at your localhost.

Since we are using Typescript, we can add the flag --template to generate a TypeScript project :

Why Typescript? Typescript extends JavaScript by adding types to the language. One advantage is that is allows early bug detection. 
If you want to learn more on the pros and cons of Typescript, check out this article.
npx create-react-app my-app --template typescript
What is npx ? This is not a typo: it’s a package runner tool that comes with npm 5.2+

This action will automatically create a ready-to-go project, with all the files below.

my-app
├── README.md
├── tsconfig.json
├── package.json
├── package-lock.json
├── .gitignore
├── node_modules
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
├── App.css
├── App.tsx
├── App.test.tsx
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── reportWebVitals.ts
└── setupTests.ts

To check that everything is ok, you can start your project with cd my-app; npm start and see your project running at http://localhost:3000 !

React setup is done!

Set-up Firebase project

To set up your Firebase project, you need to create a Firebase account (it’s free!).

From your console, you can add a new project : just follow the steps! Start adding a new project.

Note: The following snapshots were taken on December 2021.

You will be asked if you want to activate Google Analytics for your project. It is up to you to add it, even though we won’t go further on this topic. However, it won’t prevent your project from running, so you can still activate it for later.

Now that your Firebase project is created, we need to plug it to our React app! Here is what we are going to do: just select the </> web app icon and follow the steps !

You will be asked if you want to set up Firebase Hosting, you can check this option because we will use it in this tutorial, or you can activate it when the time comes.

Next step is to actually setup Firebase from your React app. You need to install firebase as a project dependency with npm install firebase, then create a firebase.ts file in your src folder to paste the Firebase configuration.

If you’ve checked the Firebase hosting option, the next steps will be about hosting. You will need to run npm install -g firebase-tools before enabling hosting.

This step explains to you how to deploy your application, but this is not mandatory for the moment so let’s deploy it later. Feel free to test this functionality, it won’t impact the next part of the tutorial.

Firebase setup is done!

Create your first Realtime database

In order to retrieve data from your database, you will need to create your database : a Realtime one !

💡 Firebase databases : Firebase gives you the opportunity to use different types of databases, the Firestore or the Realtime database. If you are not sure (or you just want a confirmation) that Realtime database is what you really need, you may want to read this Firebase documentation that explains the differences.

Go to the Realtime Database tab and select Create Database

First step is to select where you want your database stored: you can either choose United States, Belgium or Singapore. You might want to choose the closest, but it wont affect the next part of your project.

Second step is to select your mode: here we are going to use the test mode for faster setup, but you can learn more about security rules on the Firebase documentation.

Your database is ready-to-go!

Create records in your database

Remember when you had to paste your firebase configuration in a firebase.ts file ? Time to use it!

Just put this line at the end of this file, so you can reuse your configuration wherever needed.

export default app;

Our database records will be of Todo type. In order to do so, we need to define in a types.ts file:

export type Todo = {
id: string;
title: string;
done: boolean;
}

A form to create todos

Then we will need to retrieve our todos from a form. I personally suggest to use react-bootstrap library, which is useful to easily style your components. See the documentation.

We will start by creating a components folder in our src folder. Our first component will be the TodoForm, in the TodoForm.tsx file.

import { Button, Form } from "react-bootstrap";

const TodoForm = () => {
return (
<Form>
<Form.Control onChange={handleChange} />
<Button type="submit" onClick={addTodo}>
Submit
</Button>
</Form>
)
}

export default TodoForm;

Upload todos in the database

As you can see, the Form.Control and Button components call two different methods: handleChange and addTodo.

  • handleChange will be used to update the title of the todo
  • addTodo will actually push the todo in the database

Here is their implementation:

import { useState } from "react";
import { Button, Form } from "react-bootstrap";
import { getDatabase, ref, push } from "firebase/database";

// Import firebase configuration from firebase.ts file
import firebaseApp from "../firebase";

const TodoForm = () => {
const db = getDatabase(firebaseApp);

const [title, setTitle] = useState("");

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};

const addTodo = () => {
const todoRef = ref(db, "/todos");
const todo = {
title,
done: false,
};
push(todoRef, todo);
};

return (
<Form>
<Form.Control onChange={handleChange} />
<Button type="submit" onClick={addTodo}>
Submit
</Button>
</Form>
)
}

export default TodoForm;
💡 See your records from the firebase console : Each time you submit a new todo, a new record will be created in your database. You can actually see it in your firebase console.
A new record with a unique reference (-Mr49sOxtC3HZIQeYBI7 in this case) has been created. This unique reference will be useful to update a todo in a next part.

The form can now be displayed on the app. In the App.tsx file, return the new component.

// Import this if your are using react-bootstrap library
import "bootstrap/dist/css/bootstrap.min.css";

import "./App.css";
import TodoForm from "./components/TodoForm";

function App() {
return <TodoForm />
}

Read your records from your database

In order to retrieve our todos from the database, we will create a new component TodoList that will retrieve and display the data. In a TodoList.tsx file :

import { useEffect, useState } from "react";
import { getDatabase, ref, onValue } from "firebase/database";

// Import firebase configuration from firebase.ts file
import firebaseApp from "../firebase";

import { Todo } from "../types";

const TodoList = () => {
const db = getDatabase(firebaseApp);

const [todoList, setTodoList] = useState<Todo[]>([]);

useEffect(() => {
const todoRef = ref(db, "/todos");

onValue(todoRef, (snapshot) => {
const todos = snapshot.val();
const newTodoList: Todo[] = [];

for (let id in todos) {
newTodoList.push({ id, ...todos[id] });
};

setTodoList(newTodoList);
});
}, [db]);

return (
<div>
<h1>Todo List</h1>
{todoList.map((todo, index) => {
return <p key={index}>{todo.title}</p>;
})}
</div>
)
}

export default TodoList;
💡 Snapshots and values : A snapshot depicts the database from a given reference. Its type is DataSnapshot. To access the values, you need to use the val() function: it extracts a JavaScript value from a DataSnapshot. Learn more on the Firebase documentation.

Now, to display your data in your app, just add the component after the form in the App.tsx file :

// Import this if your are using react-bootstrap library
import "bootstrap/dist/css/bootstrap.min.css";

import "./App.css";
import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";

function App() {
return (
<div>
<TodoForm />
<TodoList />
</div>
);
}

Update your records

In order to have a fully functional todo list, we need to complete the tasks. Let’s improve the TodoList component by making a checklist. A new changeTodoCompletion function has been added in order to check/uncheck the todos.

import { useEffect, useState } from "react";
import { getDatabase, ref, onValue, update } from "firebase/database";
import { FormCheck } from "react-bootstrap";

import firebaseApp from "../firebase";
import { Todo } from "../types";

const TodoList = () => {
const db = getDatabase(firebaseApp);
const [todoList, setTodoList] = useState<Todo[]>([]);

useEffect(() => {
const todoRef = ref(db, "/todos");

onValue(todoRef, (snapshot) => {
const todos = snapshot.val();
const newTodoList: Todo[] = [];

for (let id in todos) {
newTodoList.push({ id, ...todos[id] });
}

setTodoList(newTodoList);
});
}, [db]);

const changeTodoCompletion = (todo: Todo) => {
const todoRef = ref(db, "/todos/" + todo.id);
update(todoRef, { done: !todo.done });
};

return (
<div>
<h1>Todo List</h1>
{todoList.map((todo, index) => {
return (
<FormCheck
key={index}
checked={todo.done}
onChange={() => changeTodoCompletion(todo)}
label={todo.title}
/>
);
})}
</div>
);
};

export default TodoList;

 

Here is how your app should look like at this point! Feel free to update CSS files in order to customize the look!

Final look of the app  — without CSS

Your todo list is ready to go live! See how to deploy it in next part.

Deploy your application

If you had not done it yet, please install the Firebase CLI with npm install -g firebase-tools. You can now run firebase login and authenticate to your Firebase account. 

To continue, you may now run firebase init. You will be able to manage different features: chose Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys.

Once you have selected hosting, you will be asked to chose your project to host: we will use an existing project.

Select your app in the list.

You will now be prompted about Hosting setup. Choose build as the public directory, then agree y to configure single-page app. You can choose to setup automatic builds and deploys with Github but this is not mandatory. If you want to see how to implement it, see you on “To go further” part. 

You can now create your production build with npm run build. Deploy your app with firebase deploy ! 🤩

You now can access your app from your hosting URL, and get more information on your app use on the project console, like check the analytics or chose a custom domain.

To go further : Continuous Deployment (CD)

If you are using a Github repository, you can chose to configure automatic deployment every time new commits are merged on your main branch: this is what we call Continuous Deployment.

To do so, you need to run firebase init and select Hosting: Set up GitHub Actions deploys.

As you already logged in to your Firebase account (if not, run firebase login), you won’t have to do anything for project setup. 

For the GitHub setup, agree to run a build before every deploy. The script to run before every deploy will be npm ci & npm run build. Now you can agree to start a deployment each time a PR is merged. Your GitHub branch associated with your app is usually main.

Once your setup is done, two Yaml files have been created: firebase-hosting-merge.yml and firebase-hosting-pull-request.yml. Commit and push them on your main branch. From your GitHub repository, you can see that a new Actions workflow has been launched!

CD is ready-to-go!


Thanks a lot for reading my article, I hope you enjoyed it and that it has been useful to you! If so, do not hesitate to share it or give a little clap!