Creating a Todo App with NextJs & Firebase

Written by anjalbinayak | Published 2022/08/25
Tech Story Tags: nextjs | firebase | chakra-ui | reactjs | react-native-development | react-native-app-development | next.js | nestjs-application

TLDRBefore we start writing code, I want to tell you that I expect you to have basic knowledge of React.Js and Next.Jsvia the TL;DR App

Hey devs, welcome to this tutorial; we will build a real-time Todo App with React, NextJs + Firebase.

The full code of this project is available in this repository

The explanation of this article is in this video

Before we start writing code, I want to tell you that I expect you to have basic knowledge of React.Js and Next.Js

If you are not familiar with React and Next, go through these documentations

  1. ReactJs Documentation
  2. NextJs Documentation

Here’s what we are going to build

After completing this tutorial, our app will look like this

Here’s our tech stack for this project

  1. ReactJs:
    We will ReactJs for building the UI of this app.
  2. NextJs:
    NextJs is a react-based framework for building modern web apps. It provides benefits likeServer Side Rendering and many SEO benefits
  3. Firebase:
    We will use firebase as a backend of this app. We will store data inFirestore**.** It provides many functions for creating real-time apps.
    (Our todo app will be real-time)
  4. ChakraUI:
    ChakraUI is a styling framework. It has styled components ready to be used in-app. It provides responsiveness, dark mode, an eye-appealing color scheme, and so on.

Why are we using Firebase in our project?

Firebase provides so much functionality that it is hard to ignore.

It provides Social Auth with just 4–5 lines of code.

We will implement Google Auth in our Project using firebase

Some features of Firebase are:

  1. Authentication
  2. Real-Time Server
  3. Low-Security Risk
  4. Social Auth
  5. Minimal Setup

Table of Contents

  1. Create Firebase Project
  2. Create Methods to Interact with Firestore
  3. Create React Components (Add Todo, Auth, TodoList)
  4. npm run dev 😁

Let’s begin with creating the next app

Type the following command to install the next app

npx create-next-app next-firebase-todo

Install the required dependencies

  1. Chakra UI
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion

2. Firebase

npm i firebase

Create Firebase Project

Go to Firebase Console

Click on Create a Project

Name your project and tick the box, and Click Continue

We don’t need Google Analytics for the Project. So, untoggle that button and click Continue

After your project has been created, you will see your project Dashboard

As Icon suggests, the first button is for the IOS project, the second for Android, and the third for Web Apps.

We are creating a web app, so we are gonna click on that.

Name your web app and click Register app

Copy this code and click on Continue to Console

Note: Save this code somewhere. We will need it later in our project.

On the left side, click on Build

We will only need two features of firebase from this list.

Authentication— For adding Google Sign In
FireStore Database — For Storing User Todos

Now, let's Enable these two features first.

Click on Authentication

Click Get Started

We are only gonna use Google Authentication, so click on Google

Enable Google Authentication, Select Project Support Email, and click Save

We have successfully enabled Google Auth in our Firebase Project

Now let us enable the Firestore Database.

Go to the left Sidebar, Click on Build, and Click Firestore Database

Click on Create database

Select start in test mode and click Next, and Click Enable

Now we have enabled both Authentication and the Firestore Database required for our Project

Now let’s start coding

Let’s go to pages/_app.js and copy-paste the following

import { ChakraProvider } from "@chakra-ui/react";
function MyApp({ Component, pageProps }) {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
);
}
export default MyApp;

We just wrapped <Component {…pageProps} /> inside <ChakraProvider>

Chakra UI’s official Documentation suggests this.

Now we are going to set up firebase in our web app.

Let us create a new folder at the project root named firebase

Create a file named index.js inside firebase directory

Now paste the code I told you to copy earlier(firebase config)

import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
// replace this firebase conFigvariable with your own
const firebaseConfig = {
apiKey: "AIzaSyClH1YdssQHAA0peMtQ_wj2A4Crnc4fEgU",
authDomain: "medium-firebase-next-todo.firebaseapp.com",
projectId: "medium-firebase-next-todo",
storageBucket: "medium-firebase-next-todo.appspot.com",
messagingSenderId: "989711683222",
appId: "1:989711683222:web:bc878dca5a5d251177fcb7",
};
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
export { auth, db };

What we did here is we just pasted the firebase config. We additionally created two variables authand dbwhich are the firebase auth module and firestore module, respectively.

Let’s add a new dependency to our project, which is react-icons

npm install react-icons --save

React Icons library lets you add beautiful icons to your project.

Now let us create methods to create and interact with todos

Methods to Interact with Firestore

Create a directory at the root of your project named api

and create a file todo.js

and copy and paste the following code in todo.js

import { db } from "../firebase";
import {
collection,
addDoc,
updateDoc,
doc,
deleteDoc,
} from "firebase/firestore";
const addTodo = async ({ userId, title, description, status }) => {
try {
await addDoc(collection(db, "todo"), {
user: userId,
title: title,
description: description,
status: status,
createdAt: new Date().getTime(),
});
} catch (err) {}
};
const toggleTodoStatus = async ({ docId, status }) => {
try {
const todoRef = doc(db, "todo", docId);
await updateDoc(todoRef, {
status,
});
} catch (err) {
console.log(err);
}
};
const deleteTodo = async (docId) => {
try {
const todoRef = doc(db, "todo", docId);
await deleteDoc(todoRef);
} catch (err) {
console.log(err);
}
};
export { addTodo, toggleTodoStatus, deleteTodo };

Now let’s create an Authentication hook for checking auth status of the user.

Create a directory named hooks

and create a file useAuth.js in hooks directory

and copy and paste the following code in useAuth.js

import { useEffect, useState } from "react";
import { auth } from "../firebase";
const useAuth = () => {
const [user, setUser] = useState(null);
const [isLoggedIn, setIsLoggedIn] = useState(false);
useEffect(() => {
auth.onAuthStateChanged((user) => {
setIsLoggedIn(user && user.uid ? true : false);
setUser(user);
});
});
return { user, isLoggedIn };
};
export default useAuth;

What we did is created a useAuth hook that returns the current user and isLoggedIn Boolean.

Every time the auth status changes, the method inside the onAuthStateChanged is called.

This functionality is given by firebase itself

Now let’s create UI

Create React Components

Create components directory at the root of your web app

We will create 3 components in our app

  1. AddTodo — for adding a new todo
  2. Auth — for adding Login/Logout Button
  3. TodoList — for viewing all existing todos properly

Let’s start with AddTodo.

Create AddTodo.jsx inside components directory

Copy and paste the following code inside AddTodo.jsx

import React from "react";
import {
Box,
Input,
Button,
Textarea,
Stack,
Select,
useToast,
} from "@chakra-ui/react";
import useAuth from "../hooks/useAuth";
import { addTodo } from "../api/todo";
const AddTodo = () => {
const [title, setTitle] = React.useState("");
const [description, setDescription] = React.useState("");
const [status, setStatus] = React.useState("pending");
const [isLoading, setIsLoading] = React.useState(false);
const toast = useToast();
const { isLoggedIn, user } = useAuth();
const handleTodoCreate = async () => {
if (!isLoggedIn) {
toast({
title: "You must be logged in to create a todo",
status: "error",
duration: 9000,
isClosable: true,
});
return;
}
setIsLoading(true);
const todo = {
title,
description,
status,
userId: user.uid,
};
await addTodo(todo);
setIsLoading(false);
setTitle("");
setDescription("");
setStatus("pending");
toast({ title: "Todo created successfully", status: "success" });
};
return (
<Box w="40%" margin={"0 auto"} display="block" mt={5}>
<Stack direction="column">
<Input
placeholder="Title"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Textarea
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<Select value={status} onChange={(e) => setStatus(e.target.value)}>
<option
value={"pending"}
style={{ color: "yellow", fontWeight: "bold" }}
>
Pending ⌛
</option>
<option
value={"completed"}
style={{ color: "green", fontWeight: "bold" }}
>
Completed ✅
</option>
</Select>
<Button
onClick={() => handleTodoCreate()}
disabled={title.length < 1 || description.length < 1 || isLoading}
variantColor="teal"
variant="solid"
>
Add
</Button>
</Stack>
</Box>
);
};
export default AddTodo;

Now create Auth.jsx inside component directory

and copy and paste the following code inside it

import React from "react";
import { Box, Button, Link, Text, useColorMode } from "@chakra-ui/react";
import { signInWithPopup, GoogleAuthProvider } from "firebase/auth";
import { FaGoogle, FaMoon, FaSun } from "react-icons/fa";
import { auth } from "../firebase";
import useAuth from "../hooks/useAuth";
const Auth = () => {
const { toggleColorMode, colorMode } = useColorMode();
const { isLoggedIn, user } = useAuth();
const handleAuth = async () => {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then((result) => {
// This gives you a Google Access Token. You can use it to access the Google API.
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
// The signed-in user info.
const user = result.user;
// ...
})
.catch((error) => {
// Handle Errors here.
const errorCode = error.code;
const errorMessage = error.message;
// The email of the user's account used.
const email = error.customData.email;
// The AuthCredential type that was used.
const credential = GoogleAuthProvider.credentialFromError(error);
// ...
});
};
return (
<Box position={"fixed"} top="5%" right="5%">
<Button onClick={() => toggleColorMode()}>
{colorMode == "dark" ? <FaSun /> : <FaMoon />}
</Button>{" "}
{isLoggedIn && (
<>
<Text color="green.500">{user.email}</Text>
<Link color="red.500" onClick={() => auth.signOut()}>
Logout
</Link>
</>
)}
{!isLoggedIn && (
<Button leftIcon={<FaGoogle />} onClick={() => handleAuth()}>
Login with Google
</Button>
)}
</Box>
);
};
export default Auth;

Now let’s create the TodoList.jsx component inside components directory.

And copy and paste the following code inside it

import {
Badge,
Box,
Heading,
SimpleGrid,
Text,
useToast,
} from "@chakra-ui/react";
import React, { useEffect } from "react";
import useAuth from "../hooks/useAuth";
import { collection, onSnapshot, query, where } from "firebase/firestore";
import { db } from "../firebase";
import { FaToggleOff, FaToggleOn, FaTrash } from "react-icons/fa";
import { deleteTodo, toggleTodoStatus } from "../api/todo";
const TodoList = () => {
const [todos, setTodos] = React.useState([]);
const {  user } = useAuth();
const toast = useToast();
const refreshData = () => {
if (!user) {
setTodos([]);
return;
}
const q = query(collection(db, "todo"), where("user", "==", user.uid));
onSnapshot(q, (querySnapchot) => {
let ar = [];
querySnapchot.docs.forEach((doc) => {
ar.push({ id: doc.id, ...doc.data() });
});
setTodos(ar);
});
};
useEffect(() => {
refreshData();
}, [user]);
const handleTodoDelete = async (id) => {
if (confirm("Are you sure you wanna delete this todo?")) {
deleteTodo(id);
toast({ title: "Todo deleted successfully", status: "success" });
}
};
const handleToggle = async (id, status) => {
const newStatus = status == "completed" ? "pending" : "completed";
await toggleTodoStatus({ docId: id, status: newStatus });
toast({
title: `Todo marked ${newStatus}`,
status: newStatus == "completed" ? "success" : "warning",
});
};
return (
<Box mt={5}>
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={8}>
{todos &&
todos.map((todo) => (
<Box
p={3}
boxShadow="2xl"
shadow={"dark-lg"}
transition="0.2s"
_hover={{ boxShadow: "sm" }}
>
<Heading as="h3" fontSize={"xl"}>
{todo.title}{" "}
<Badge
color="red.500"
bg="inherit"
transition={"0.2s"}
_hover={{
bg: "inherit",
transform: "scale(1.2)",
}}
float="right"
size="xs"
onClick={() => handleTodoDelete(todo.id)}
>
<FaTrash />
</Badge>
<Badge
color={todo.status == "pending" ? "gray.500" : "green.500"}
bg="inherit"
transition={"0.2s"}
_hover={{
bg: "inherit",
transform: "scale(1.2)",
}}
float="right"
size="xs"
onClick={() => handleToggle(todo.id, todo.status)}
>
{todo.status == "pending" ? <FaToggleOff /> : <FaToggleOn />}
</Badge>
<Badge
float="right"
opacity="0.8"
bg={todo.status == "pending" ? "yellow.500" : "green.500"}
>
{todo.status}
</Badge>
</Heading>
<Text>{todo.description}</Text>
</Box>
))}
</SimpleGrid>
</Box>
);
};
export default TodoList;

We have created all the required components, now we are gonna place them in our app.

Go to pages/index.js and remove all the contents and paste the following content

import { Container } from "@chakra-ui/react";
import AddTodo from "../components/AddTodo";
import Auth from "../components/Auth";
import TodoList from "../components/TodoList";
export default function Home() {
return (
<Container maxW="7xl">
<Auth />
<AddTodo />
<TodoList />
</Container>
);
}

And we are done.

npm run dev

We have completed the coding of the app.

Go to your terminal and type

npm run dev

and goto localhost:3000 in your browser

You will see something like this

Now you can log in with Google and start adding Todo’s

Get the full code here

Watch the explanation video here

Follow me on Twitter


Also Published Here


Written by anjalbinayak | Writing Online & Writing Softwares
Published by HackerNoon on 2022/08/25