Build A Todo Web App Using Next.js, Firebase, Chakra UI + React Hooks

Use the latest and greatest technologies to build a todo web app.

Build A Todo Web App Using Next.js, Firebase, Chakra UI + React Hooks

Introduction

Building a todo app is common practice when you are trying to learn a new tech stack. In this tutorial, I'll show you how to build a todo app using the follwing stack:

  • Next.js for the JavaScript framework
  • Chakra-UI for the CSS framework
  • Firebase for the auth and backend
  • React hooks for handling data locally

Let's jump into coding!

Bootstrapping With next-firebase-auth

I will be building this like any other project. This means, I will not be starting from scratch. Instead, we will be cloning this repo which has Firebase Auth already implemented. Go ahead and clone the repo and then cd into the example folder. This is the Next.js source code.

As a reminder, you can clone this repo from your terminal using:

git clone https://github.com/gladly-team/next-firebase-auth.git

then...

cd example

Once you have the starter files, feel free to open them up in the IDE of your choice.

.env.local

You will notice that if you try to run the app (using yarn dev) you will get an error. This is because we need to edit the secret values inside of .env.local.example to our Firebase values. First, copy everything inside of .env.local.example into a new file named .env.local.

.env.local.example
# Copy this file as ".env.local".
# Update these with your Firebase app's values.
FIREBASE_CLIENT_EMAIL=my-example-app-email@example.com
NEXT_PUBLIC_FIREBASE_PUBLIC_API_KEY=MyExampleAppAPIKey123
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=my-example-app.firebaseapp.com
NEXT_PUBLIC_FIREBASE_DATABASE_URL=https://my-example-app.firebaseio.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=my-example-app-id

# Your Firebase private key.
# When using Vercel, add the private key with double quotes
# via the CLI, not the Vercel dashboard:
#   vercel secrets add firebase-private-key '"my-key-here"'
# Then, use `JSON.parse` in the app. See:
# https://github.com/vercel/vercel/issues/749#issuecomment-707515089
FIREBASE_PRIVATE_KEY='"some-key-here"'

# Secrets used to sign cookies.
COOKIE_SECRET_CURRENT=someSecretValue
COOKIE_SECRET_PREVIOUS=anotherSecretValue

# Cookie options.
NEXT_PUBLIC_COOKIE_SECURE=false # set to true in HTTPS environment

Next, create a Firebase project and create a web project inside of it.

Image of creating a firebase web app

Go ahead and turn on Email/Password auth, cloud firestore and realtime db.

Image of left sidebar

Now, go into project settings. In the general tab, you will find most of your secret values.

Image of secrets

To get the remaining values, click the "Service Accounts" tab and "Generate a New Private Key." Open the file that is downloaded. The remaining secret values such as the api key (the super long string) can be found in here.

Image of generating a private key

At this point, you should be able to log into the site using email and password.

Adding Chakra-UI

Add Dependencies

Once this is complete, we can add Chakra-UI to our project. First add the dependencies:

yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^4

Wrap With ChakraProvider

Next, wrap _app.js with the <ChakraProvider>.

_app.js
import React from 'react'
import '../styles/globals.css'
import initAuth from '../utils/initAuth'
import { ChakraProvider } from "@chakra-ui/react" // <- add this

initAuth()

function MyApp({ Component, pageProps }) {
  // eslint-disable-next-line react/jsx-props-no-spreading
  return (
    <ChakraProvider> // <- add this
      <Component {...pageProps} />
    </ChakraProvider> // <- add this
  )
}

export default MyApp

Creating todo.js

All of the code in this turoial will be added in a file named todo.js. You can go ahead and create this inside of the pages directory. We will start by adding a bunch of imports.

Adding The Imports

todo.js
import React, { useState, useEffect } from 'react'
import {
    Flex,
    Heading,
    InputGroup,
    InputLeftElement,
    Input,
    Button,
    Text,
    IconButton,
    Divider,
} from "@chakra-ui/react"
import { AddIcon, DeleteIcon, StarIcon } from "@chakra-ui/icons"
import {
    useAuthUser,
    withAuthUser,
    withAuthUserTokenSSR,
    AuthAction,
} from 'next-firebase-auth'
import getAbsoluteURL from '../utils/getAbsoluteURL'
import firebase from 'firebase/app'
import 'firebase/firestore'
  • line 1 are react imports
  • lines 2-13 are chakra ui imports
  • lines 14-20 are next-firebase-auth imports
  • lines 21-22 are firebase imports

Defining Our Body

Next, define our react functional component with some basic styles:

todo.js
// imports here

const Todo = () => {
    return (
        <Flex flexDir="column" maxW={800} align="center" justify="center" minH="100vh" m="auto" px={4}>

        </Flex>
    )
}

export const getServerSideProps = withAuthUserTokenSSR()

export default withAuthUser()(Todo)

I'll go ahead and add the rest of the UI elements.

todo.js
<Flex flexDir="column" maxW={800} align="center" justify="center" minH="100vh" m="auto" px={4}>
    <Flex justify="space-between" w="100%" align="center">
        <Heading mb={4}>Welcome, {AuthUser.email}!</Heading>
        <IconButton ml={2} onClick={AuthUser.signOut} icon={<StarIcon />} />
    </Flex>

        <InputGroup>
            <InputLeftElement
                pointerEvents="none"
                children={<AddIcon color="gray.300" />}
            />
            <Input type="text" placeholder="Learn Chakra-UI & Next.js" />
            <Button
                ml={2}
            >
                Add Todo
            </Button>
        </InputGroup>
</Flex>

If you run the app you should see some nice output!

Writing Data to Firebase's Cloud Firestore

useState

To write data to cloud firestore, we will first need to get the data from the user and hold it locally. We can use useState for this. Add the following code:

todo.js
const [input, setInput] = useState('')
const [todos, setTodos] = useState([])

Now we can store the users input value in the input variable and when we fetch the data back, we can store it in the todos variable.

onClick + onChange

To get the data, we can use onClick and onChange.

todo.js
<InputGroup>
        <InputLeftElement
            pointerEvents="none"
            children={<AddIcon color="gray.300" />}
        />
        <Input type="text" onChange={(e) => setInput(e.target.value)} placeholder="Learn Chakra-UI & Next.js" />
        <Button
            ml={2}
            onClick={() => sendData()}
        >
            Add Todo
        </Button>
    </InputGroup>

If you add some console.logs and type in some data, you will see that this works! The next step is to create the sendData method and send the data to Firebase.

We can wrap everything inside of a try catch loop to handle errors. To interact with Firestore, we can use firebase.firestore().

We will store each users todo's inside of their own collection using their id. Each individual toto will be stored inside a doc, using the todo name and the doc name.

Sending The Data

Lastly, use .set to set the data! We'll pass in the timestamp as well so we can order todos better later on.

todo.js
const sendData = () => {
        try {
            // try to update doc
            firebase
                .firestore()
                .collection(AuthUser.id) // each user will have their own collection
                .doc(input) // set the collection name to the input so that we can easily delete it later on
                .set({
                    todo: input,
                    timestamp: firebase.firestore.FieldValue.serverTimestamp(),
                })
                .then(console.log('Data was successfully sent to cloud firestore!'))
        } catch (error) {
            console.log(error)
        }
    }

Fetching Data from Firebase's Cloud Firestore

We can fetch data in a similar fashon. We'll fetch everything inside of useEffect so we can listen to changes in real time.

todo.js
useEffect(() => {
        AuthUser.id &&
            firebase
                .firestore()
                .collection(AuthUser.id)
                .orderBy('timestamp', 'desc')
                .onSnapshot(snapshot => {
                    setTodos(snapshot.docs.map(doc => doc.data().todo))
                })
    })

Now, we have all the todo items inside of the todos variable! Let's map over them and display it to the user.

Displaying Todos To User

todo.js
{todos.map((t, i) => {
                return (
                    <>
                        {i > 0 && <Divider />}
                        <Flex
                            key={i}
                            w="100%"
                            p={5}
                            my={2}
                            align="center"
                            borderRadius={5}
                            justifyContent="space-between"
                        >
                            <Flex align="center">
                                <Text fontSize="xl" mr={4}>{i + 1}.</Text>
                                <Text>{t}</Text>
                            </Flex>
                            <IconButton onClick={() => deleteTodo(t)} icon={<DeleteIcon />} />
                        </Flex>
                    </>
                )
            })}

Adding Delete

Lastly, let's add delete functionality. as you can see, we already have the deleteTodo method. Let's create this above.

To delete, we can grab the collection, grab the doc, and call .delete. Recall the collection name is the user's id and the doc name is the todo.

todo.js
const deleteTodo = (t) => {
        try {
            firebase
                .firestore()
                .collection(AuthUser.id)
                .doc(t)
                .delete()
                .then(console.log('Data was successfully deleted!'))
        } catch (error) {
            console.log(error)
        }
    }

Conclusion

We have now created a todo web app with Firebase Auth, Next.js as the JS framework, Chakra UI as the CSS framework, Firebase Cloud Firestore as the backend, and React Hooks to handle data locally. If you want to look at any of these in more detail, check out the recommended posts below!


View Related Posts

  • Advanced Chakra UI

    chakra-ui
  • Chakra-UI Mobile Navbar (Drawer Component)

    chakra-ui
  • Chakra-UI Responsive Navigation Bar

    chakra-ui
  • Add Google Analytics To Next.js Website

    nextjs
  • Add Splitbee Analytics To Next.js

    splitbee
  • Generate A Dynamic Sitemap In Next.js Website

    nextjs
  • Next.js + MDX Pages Quickstart

    nextjs
  • useEffect() Hook in Next.JS - React Hooks

    react
  • How To Use Firebase As Flutter's Backend

    flutter


Written By Benjamin Carlson

Founder coffeeclass.io

More Articles By Benjamin Carlson



Legal

Terms

Disclaimer

Privacy Policy

Carlson Technologies Logo© Copyright 2021, Carlson Technologies LLC. All Rights Reserved.

Powered by

Vercel Logo