Build a ToDo App With React and Firebase
Tags: react, javascript, webdev, beginners
Published: 2023-03-02 09:36:40

In today's fast-paced world, keeping track of our daily tasks and responsibilities is more important than ever. Whether you're a student, a professional, or a homemaker, having a reliable task management system can help you stay organized and focused on what matters most. One way to achieve this is by using a ToDo application.

In this article, I will guide you through the process of building a ToDo list app using React, with the data stored in the backend, specifically in a Firebase Firestore database.

The ToDo List app will allow users to enter a Todo item and store it in a lovely list in the firebase database. So, this list will be permanent and won’t be changed after a refresh of the browser. Users can also delete it.

The topics covered in this article include the following:

- Using a React useRef hook.
- Storing data in a Firebase Firestore database.
- Using MUI icons and components
- And much more.

### Overview
The final result will look like this:

![Todo App Overview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00urgki6lf427x424feu.jpg)

### Project folders structure
As we go through the implementation of our application, we will end up with the following folder structure containing files:

![Folder structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhodmrjzz6cv5amgqfzl.jpg)

### Initializing React application
To get started, use the create-react-app command to create a new app called todo-app. Specifically, the command for this is as follows:

```
npx create-react-app todo-app
```

### Initializing Firebase
Before setting up Firebase, let us talk a bit about it.

Firebase is a set of tools developed by Google that provided backend service. It contains a variety of services such as:

- Cloud Firestore: Real-time, cloud-hosted, NoSQL database
- Cloud storage: Massively scalable file storage
- Cloud functions: Serverless, event-driven back-end functions
- Authentication: User login and identity
- Firebase hosting: Global web hosting
- ML Kit: An SDK for common machine learning tasks

Firebase makes life so easy for Front-end developers to integrate a backend into their application without creating any API routes or other backend code.

So, we will use the cloud Firestore to store data in it.
Now go to [Firebase website](https://firebase.google.com/) and create a new account. Create a new project by clicking add project. After this click on Continue to the console button. Scroll down and click the config radio button and then copy all the data for the firebase config section.

Now, inside the src folder, create a file called _firebase.js_ and add paste the copied code to it. This code will look like this:

_firebase.js_

```javascript
const firebaseConfig = {
    apiKey: "long-random-key",
    authDomain: "default-domain-of-your-app",
    projectId: "name-project -id",
    storageBucket: "storage-bucket",
    messagingSenderId: "messageSenderId",
    appId: "random-key",
    measurementId: "random-key"
};
```

### Basic React Setup
Now, we will do basic setup for React. Inside the ToDo app directory delete all unnecessary files. So our _index.js_ and _App.js_ files look like this:

_src/index.js_

```javascript
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render( <
    React.StrictMode >
    <
    App / >
    <
    /React.StrictMode>,
    document.getElementById('root')
);
```
_src/App.js_

```javascript
function App() {
    return ( <
        div className = "App" >
        <
        h2 > TODO List App < /h2> <
        /div>
    );
}
export default App;
```

### Local ToDo List
After doing the setup in the previous section, we will work on our ToDo app. We will update our _App.js_ file to contain the logic for a basic ToDo list. Here, we are using two state variables: _todos_ and _input_. We are using the _useState_ hook to declare both of them. todos contains an array containing two strings, and input contains an empty string.

Next, inside the return statement, we use the controlled input of React to update the input of an input box. Next, we have a button and a click event assigned to the button. When we click it, we will run a function called _addTodo()_ that changes the state of _todos_, with _setTodos_. Here, it appends the already existing content with the user-typed content.

We are using a form to wrap our input and button, and the button type is submitted. Therefore, if we type anything in the input box and press Enter on the keyboard, it will work.

For creating the user interface of our app, we will use a pre-build components library: MUI.

MUI formerly called Material UI is a React component Library developed by Google in 2014 that offers accessible, robust, production-ready, customizable, and reusable code components for faster web development. It uses grid-based layouts, animations, transitions, padding, and many more. It also specifies a large set of standard icons.

Let’s install it by using this command:

```
npm install @mui/icons-material
npm install @mui/material @emotion/react @emotion/styled
```

All we have said in the previous line is resumed by the following code:

```javascript
import React, { useState } from 'react';
import { TextField, Button } from '@mui/material';
import './App.css';
function App() {
    const [todos, setTodos] = useState([
        'Create Blockchain App',
        'Create a Youtube Tutorial'
    ]);
    const [input, setInput] = useState('');
    const addTodo = (e) => {
        e.preventDefault();
        setTodos([...todos, input]);
        setInput('')
    };
    return (
        <div className="App">
            <h2> TODO List App</h2>
            <form>
                <TextField id="outlined-basic" label="Make Todo" variant="outlined" style={{ margin: "0px 5px" }} size="small" value={input}
                    onChange={e => setInput(e.target.value)} />
                <Button variant="contained" color="primary" onClick={addTodo}  >Add Todo</Button>
            </form>
            <ul>
                {todos.map(todo => <li>{todo}</li>)}
            </ul>
        </div>
    );
}
export default App;
```
Now, run the app in your terminal. 

```
npm run start
```
It looks like this:

![Overview 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sg1eivnu8dubl9hbojph.jpg)

### ToDo Components
Next, we will move the ToDo list to a separate component. So, create a file named _Todo.js_ inside a component folder. We will send the separate ToDo to it as a props.

The updated code is shown as:

```javascript
import React, { useState } from 'react';
import { TextField, Button } from '@mui/material';
import Todo from './components/Todo';
import './App.css';
function App() {
    const [todos, setTodos] = useState([
        'Create Blockchain App',
        'Create a Youtube Tutorial'
    ]);
    const [input, setInput] = useState('');
    const addTodo = (e) => {
        e.preventDefault();
        setTodos([...todos, input]);
        setInput('')
    };
    return (
        <div className="App">
            <h2> TODO List App</h2>
            <form>
                <TextField id="outlined-basic" label="Make Todo" variant="outlined" style={{ margin: "0px 5px" }} size="small" value={input}
                    onChange={e => setInput(e.target.value)} />
                <Button variant="contained" color="primary" onClick={addTodo}  >Add Todo</Button>
            </form>
            <ul>
                {todos.map(todo => <Todo todo={todo} />)}
            </ul>
        </div>
    );
}
export default App;
```
Now add the following code into the Todo.js file. We are just using a bunch of mui icons and showing the props called todo. These icons help us to make the list item prettier.

_src/components/Todo.js_

```javascript
import { List, ListItem, ListItemAvatar, ListItemText } from '@mui/material';
const Todo = ({ todo }) => {
    return (
        <List className="todo__list">
            <ListItem>
                <ListItemAvatar />
                <ListItemText primary={todo} secondary={todo} />
            </ListItem>
        </List>
    )
};
export default Todo;
```
Run our app, now it looks like this:

![Overview 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzexpecsy4oywv06xfc0.jpg)

Now, it’s time to hook up Firebase to the project.

We will start setting up Firebase for the backend. For that, install all the necessary dependencies.

```
npm install firebase
```
Update our _firebase.js_ file to use the config to initialize the app. After that, we will use Firestore as the database.

```javascript
import { initializeApp } from "firebase/app";
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = {
...........
............
};
const firebaseApp = initializeApp(firebaseConfig);
const db = getFirestore(firebaseApp);
export { db }
```

Now, we will go back to Firebase and click _Cloud Firestore_ and then click the _Create database_ button, as shown in following picture:

![create database](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qswrd0v2fc6oqzhhgwd6.jpg)

On the next screen, select Start in _test mode_ and then click the Next button, as shown:

![Firebase test mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/usd3q323fnp68zok1wu0.jpg)

On the next screen, click the Enable button:

![Firebase enable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ogsik72xduxwe0bw9j6.jpg)
On the next screen, click _Start collection_, as shown in following picture:

![Firebase create collection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4trappbf34vbjcu4ejjc.jpg)
It will open the pop-up shown in the following picture.

![Create ID](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqcj8q7rbc802i7ll7hp.jpg)
We need to enter todos in the Collection ID field and click the Next button.

On the next screen, fill the Document ID field by clicking Auto ID. Also enter _todo_ in the Field, choose string for the type and add a random text in the value field After that, click the Save button.That will take us back to the main screen.

### Adding Firebase to the App
Now, we are going to use the data from Firebase database. So, remove the hard-coded stuff in the _useState_ code for todos. After that, within _useEffect_, we are calling the collection todos , and then we take the snapshot. In Firebase terms, it is the live data, which we will get instantly. We will then set this data in the todos array, via _setTodos()_.

Also, notice that _useEffect_ has input inside the array. So, any time a todo is added by the user, it will instantly display in our app. Notice that we have changed the way we loop through data, using todos. This is done because we receive the data as an array of objects.

Updated code:
_src/App.js_

```javascript
import React, { useState, useEffect } from 'react';
import { TextField, Button } from '@mui/material';
import Todo from './components/Todo';
import { db } from './firebase.js';
import { collection, onSnapshot } from 'firebase/firestore';
import './App.css';
function App() {
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState('');
    useEffect(() => {
        onSnapshot(collection(db, 'todos'), (snapshot) => {
            setTodos(snapshot.docs.map(doc => doc.data()))
        })
    }, [input]);
    const addTodo = (e) => {
        e.preventDefault();
        setTodos([...todos, input]);
        setInput('')
    };
    return (
        <div className="App">
            <h2> TODO List App</h2>
            <form>
                <TextField id="outlined-basic" label="Make Todo" variant="outlined" style={{ margin: "0px 5px" }} size="small" value={input}
                    onChange={e => setInput(e.target.value)} />
                <Button variant="contained" color="primary" onClick={addTodo}  >Add Todo</Button>
            </form>
            <ul>
                {todos.map(({ todo }) => <Todo todo={todo} />)}
            </ul>
        </div>
    );
}
export default App;
```
Now, we will add the functionality so the user can add the ToDo item. For that, we just need to add the input to the collection, using _addDoc()_.

Also, notice that we are adding the server timestamp, while adding a ToDo. We are doing this because we need to order the ToDos in descending order.

The resulted code looks like this:
src/App.js

```javascript
import React, { useState, useEffect } from "react";
import { TextField, Button } from "@mui/material";
import Todo from "./components/Todo";
import { db } from "./firebase";
import {
  collection,
  onSnapshot,
  serverTimestamp,
  addDoc,
} from "firebase/firestore";
import "./App.css";
function App() {
  const [todos, setTodos] = useState([]);
  const [input, setInput] = useState("");
  useEffect(() => {
    onSnapshot(collection(db, "todos"), (snapshot) => {
      setTodos(snapshot.docs.map((doc) => doc.data()));
    });
  }, [input]);
  const addTodo = (e) => {
    e.preventDefault();
    addDoc(collection(db, "todos"), {
      todo: input,
      timestamp: serverTimestamp(),
    });
    setInput("");
  };
  return <div className="App">............</div>;
}
export default App;
```
Now, we need to get the ID of each item that we require for the key and also for deleting, which we are going to implement. The key is essential in React for optimization, and we also get a warning in the console.
So, we need to change the structure in which we set the data in _setTodos()_.

_src/App.js_

```javascript
import React, { useState, useEffect } from 'react';
import { TextField, Button } from '@mui/material';
import Todo from './components/Todo';
import { db } from './firebase.js';
import { collection, query, orderBy, onSnapshot, addDoc, serverTimestamp } from 'firebase/firestore';
import './App.css';
const q = query(collection(db, 'todos'), orderBy('timestamp', 'desc'));
function App() {
    const [todos, setTodos] = useState([]);
    const [input, setInput] = useState('');
    useEffect(() => {
        onSnapshot(q, (snapshot) => {
            setTodos(snapshot.docs.map(doc => ({
                id: doc.id,
                item: doc.data()
            })))
        })
    }, [input]);
    const addTodo = (e) => {
        e.preventDefault();
        addDoc(collection(db, 'todos'), {
            todo: input,
            timestamp: serverTimestamp()
        })
        setInput('')
    };
    return (
        <div className="App">
            <h2> TODO List App</h2>
            <form>
                <TextField id="outlined-basic" label="Make Todo" variant="outlined" style={{ margin: "0px 5px" }} size="small" value={input}
                    onChange={e => setInput(e.target.value)} />
                <Button variant="contained" color="primary" onClick={addTodo}  >Add Todo</Button>
            </form>
            <ul>
                {todos.map(item => <Todo key={item.id} arr={item} />)}
            </ul>
        </div>
    );
}
export default App;
```

### Delete functionality
Next, we will add the delete functionality, in which we will have to get the ID of the item and call the _deleteDoc_. The updated code looks like this.

_src/Todo.js_

```javascript
import { List, ListItem, ListItemAvatar, ListItemText } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { db } from "../firebase.js";
import { doc, deleteDoc } from "firebase/firestore";
const Todo = ({ arr }) => {
  return (
    <List className="todo__list">
      <ListItem>
        <ListItemAvatar />
        <ListItemText primary={arr.item.todo} secondary={arr.item.todo} />
      </ListItem>
      <DeleteIcon
        fontSize="large"
        style={{ opacity: 0.7 }}
        onClick={() => {
          deleteDoc(doc(db, "todos", arr.id));
        }}
      />
    </List>
  );
};
export default Todo;
```
Now, open your terminal and run the application. Our app is now fully functional, but the design looks awful. So, we need to add some styles.

![Aweful design](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f70d8k5ieurn2z1qjyod.jpg)
Create a file called todo.css and imports it to the Todo.js file with the following styles:

```css
.todo__list{
display:flex;
justify-content: center;
align-items: center;
width: 800px;
border: 1px solid lightgray;
margin-bottom: 10px !important;
}
```
Replace all the styles in the App.css with the following:

```css
.App {
display:grid;
place-items:center;
}
```
Run the application, now it looks much nicer.

![cool design](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/038tv1xw99yufawxs17y.jpg)

## Conclusion
Now that our Todo application can save its data in Firestore database, we can think about the next phase about making this a real application- namely authenticating our users. But to make this article as simple as possible, we will not implement that functionality. Make it in your way. I would be happy to see the final result.

**THANK YOU FOR READING** 
I hope you found this little article helpful.  Please share it with your friends and colleagues. Sharing is caring.

Connect with me on [various platforms](https://bio.link/thomassentre)