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
After completing this tutorial, our app will look like this
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:
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
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
2. Firebase
npm i firebase
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 auth
and db
which 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
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 components
directory at the root of your web app
We will create 3 components in our app
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.
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