React Notes app with authentication and storage

Introduction

This tutorial is going to be a guide on how to create a simple notes application also provided with firebase authentication. This topic covers basic essential concepts like prop drilling, state hooks and javascript higher order functions like the map and filter. Though a simple and basic app, I particularly picked this project because it tends to cover many basic features for beginners and experts and also treats the firebase authentication with google sign up. Understanding this tutorial will help you solve any mundane task that could come your way. I advise that you practise this course which you can also add to your portfolio. I will be discussing more of functionality than styling. You can fork or copy the app in my github repo.

Creating react notes application

To start this project, run npx create-react-app appname. I talked about how to install react on your pc here. Another essential tool for this development is tailwind for our styling, to install it, run these commands simultaneously

npm install -D tailwindcss
npx tailwindcss init

Tailwind provides an intuitive documentation on how to set it up here. The last tool for this project is the firebase, to install it, run npm i firebase and we are set to build a notes application. I am very excited for the next phase.

Configuring firebase

After installing firebase, we have little configurations to create on the website. To start, firebase requires that we create a project on the website, I will call this react-todoapp, after registering the app, you are immediately provided with a config file, this is the basis file for building our firebase auth, in the src folder, I will be creating a services folder and firebase.js file where I will copy the configuration file, this config file can also be added directly to your app.js, it all depends on preference. I will also import some packages to this file and initialize my auth before exporting it.

import { initializeApp } from "firebase/app";

import {getAuth} from 'firebase/auth'

These are firebase packages for initializing auth, the full file looks like this,

import { initializeApp } from "firebase/app";

import {getAuth} from 'firebase/auth'

const firebaseConfig = {

    apiKey: process.env.APIKEY,
    authDomain: process.env.AUTHDOMAIN,
    projectId: process.env.PROJECTID,
    storageBucket: process.env.STORAGEBUCKET,
    messagingSenderId: process.env.MESSAGINGSENDERIS,
    appId: process.env.APPID,
    measurementId: process.env.MEASUREMENTID,

  };



  const firebaseApp = initializeApp(firebaseConfig);

  export const auth = getAuth(firebaseApp);

With the package I imported, I initialized my config and exported it, this will be useful in authentication going forward.

Creating application logic

A notes application is basically a list of notes displayed in a stack of items and arranged accordingly, to create this, I will be using an array, array in javascript has powerful functions to help create this application. With the use of the useState hook, I will create a listItems state where each notes will be added to display to user To use the useState hook, we start with importing the useState hook

import {useState} from 'react'

let [listItems,setListItems]=useState([])

The useState hook uses a destructuring method to access and set items, the listItems is the array and the setListItems is the setter, I mentioned in my post here that react treats data as immutable. we will explore more on this as we move on. Since this is a note app, it will be nice for us to be able to see our recorded notes on a later date logging in to the platform, to persist data on this app, we will be using the localStorage API to keep our data local to that particular browser so that we can access it when going back to the website.

Note

Firebase provides a store function called firestore but we will not be exploring it here

localStorage API

The localstorage is a browser API that allows you to use browser storage to store files with get and set methods

localStorage.setItem(itemName,item)- // for adding a string to the API, it requires a name for setting and retrieving item;
localStorage.getItem(itemName)-  // for getting stored items;
localStorage.removeItem(itemName)- // for deleting an initial stored item

However, the API stores only strings which leads us to using Json methods, JSON.stringify() to convert our arrays to strings and JSON.parse() to retrieve them from string methods. The useState hook basically accepts a function as long as it returns a value

import {useEffect,useState} from 'react'

function App(){
let store=localStorage.getItem('Todo')
let [listItems,setListItems]=useState(()=>{
if(store){
return store;
} else {
return [];
}
});

useEffect(()=>{
localStorage.setItem('Todo',JSON.stringify(listItems))
})

return (
<div></div>
)
};

This method is basically means that on initialization get the Todo property from localStorage then on the useState hook, if the store from the todo property is available, initialize it as the array else set an empty array.

With these methods set in place, we basically have a basic front end CRUD app, we can return a list item with the use of map. The map function is a higher order function that helps transform an array and returns another array. I will write more on this in the future. A major challenge I noticed is that newbie devs don't know how to return values, I also faced this challenge, when using map, to return an item is something like this

listItems.map((val)=><li>{val}<li>)
or
listItems.map((val)=>(
<li>{val}<li>
))

This will work because arrow functions has an implicit return and the second method also works. Another majorly used method is this

listItems.map(val=>{
return (<div>{val}</div>)
})

Because of the bracket, I have to return a value hence this one. Moving on to the project ;

return (
<div>
{
listItems.map((val,i)=>{
return (
    <div className="items flex justify-evenly p-4 border-slate-300 border w-fit rounded-md mt-2 shadow-md dark:shadow-sm dark:shadow-white" key={i}>
    <div className="w-11/12 mr-4 dark:text-white leading-6">
                    {val}
                  </div>
                  <button className=" p-1 rounded-md"
                    onClick={() => {
                      setListItems(listItems.filter((val, id) => i !== id));
                    }}>
                    <MdDelete className="dark:text-white" />
                  </button>
                </div>
)
})
}
</div>
)

That is all there is to the note app, the classes there are tailwind classes. I added a method to the button. This method is meant to delete unwanted notes from our note app however this method may seem unconventional, to carry out an operation like this, we are supposed to have an array of objects with an incrementing or unique ID for tracking our items

listItems.filter((val,id)=>i!==id)

This method filters id crosschecking id from our mapped array thereby giving us a new array so we set this new array to the listItems

setListItems(listItems.filter((val, id) => i !== id));

We need to create a modal for an input element to add notes to this app. This part can be a bit confusing as it involves prop drilling, to create the modal, we are creating a new component called the Modal.jsx. Prop drilling is basically passing data from a component to another component.

I will drop the component for the modal here

import React, { useState } from "react";

import { VscChromeClose } from "react-icons/vsc";





const Modal = ({ show, onChange,onClose }) => {
  let [inputValue, setInputValue] = useState("");
  let handleData = (e) => setInputValue(e.currentTarget.value);
  return (
    <div
      className={`p-10 shadow-sm dark:shadow-white dark:shadow-sm rounded-lg z-30 dark:bg-slate-600 shadow-black md:overflow-y-auto h-80 max-h-96 blur-none bg-white  flex m-auto fixed  md:left-72 top-28 left-16 w-3/4 md:w-2/4 ${

        show ? "block" : "hidden"

      }`}

    >

      <div className="w-full">

        <div className="header flex justify-center relative">

          <h2 className="font-bold dark:text-white">Add an Item</h2>

          <button className="absolute right-0 hover:bg-red-300 rounded-full p-2 hover:text-white" onClick={onClose}><VscChromeClose className="dark:text-white" /></button>

        </div>
        <main className=" p-6 mt-6 flex flex-col relative h-3/4">
          <div className="h-3/4 w-11/12 m-auto ">
          <input
            type="text"
            name=""
            id=""
            placeholder="What's happening?"
            onChange={(e) => handleData(e)}
            value={inputValue}
            className="outline-none pl-4 text-start dark:bg-slate-500 dark:text-white rounded-md w-full h-3/4"
          />
          </div>
          <div className="absolute md:bottom-0 top-40 md:left-96">
            <button
            className="p-4 text-white font-bold bg-blue-400 rounded-3xl dark:text-gray-200"
              onClick={() => {
              if (inputValue.length!==0) {
                  onChange(inputValue);
                  setInputValue("");
                }
              }}
            >
              Post Item
            </button>
          </div>
        </main>
      </div>
    </div>
  );
};

export default Modal;

With the use of state, we set our input value to state then pass a method to the button that passes this data to the parent component.

 let [inputValue, setInputValue] = useState("");
  let handleData = (e) => setInputValue(e.currentTarget.value);

The handleData is added to the onChange method of the input value then we create a method for the submit button

 onClick={() => {
              if (inputValue.length!==0) {
                  onChange(inputValue);
                  setInputValue("");
                }
              }}

This means that if our inputvalue state's length is not 0, execute this method, we then invoke the onChange and setInputValue to string , this is different from the onchange event listener, this is a prop passed from the App parent component like this

<Modal show={show} onChange={handleChange} onClose={closeToggle} />

The method handleChange was passed as a prop to the modal component. The handleChange method passes the data from the inputValue state to the listItems state with the rest method

 let handleChange = (e) => {
    setListItems([...listItems, e]);
    setShow(!show);
  };

The setShow method is responsible for toggling the modal with the closeToggle method

 let closeToggle = () => setShow(!show);

Authentication

To authenticate this app, we will be using firebase which will setup earlier, firebase provides a rich amount of authentication methods to draw from, we will be using google auth for this project, firebase as different methods for this, we will be using signInWithPopup method.

We import some methods first and also import our auth config file we set up initially

import { signInWithPopup, GoogleAuthProvider } from "firebase/auth";
import { auth } from "./services/firebase";

Then we create a signIn button that stands as a wall to accessing our notes application

let signInWithGoogle = () => {
    signInWithPopup(auth, provider)
      .then((result) => {
        const user = result.user;
        setVerifiedUser(user);
      })
      .catch((error) => {
        const errorCode = error.code;
        console.log(error, errorCode);
      });
  };

The signInWithPopup returns a promise which we can attach then and catch methods, we set the result gotten to a state hook, setVerifiedUser(result) . To create the wall, we use an if statement which checks if the verifieduser user is available

 if (!verifiedUser) {
    return (
      <div className="w-screen p-4 justify-center content-center flex h-screen">
        <div className="w-11/12 flex flex-col justify-center content-center">
          <div className="w-full p-4 h-2/3 flex content-center justify-center">
            <div className="max-w-[80%] m-auto h-full">
            <img src={Man} alt="" className="w-full" />
            </div>
          </div>
          <div className="flex flex-col justify-center content-center">
            <h1 className="font-bold text-center text-xl mb-4">The Notepad</h1>
            <button
              className="p-4 text-white w-32 rounded-lg capitalize self-center text-center bg-blue-500 font-bold"
              onClick={() => signInWithGoogle()}>
              get started
            </button>
          </div>
        </div>
      </div>
    );
  }

This whole process works in localhost but when deployed, firebase requires that you add your website address to the accessibility settings in your console for firebase.

Firebase returns some methods like the photoURL, displayName, emailId among other methods which you can plug to your app. I used the photoURL and displayName in this app. You can explore it how you like

Firebase also provides a hook to do all the background work for you

First we install firebase-hooks

npm i firebase-hooks

Then we import this and add some methods, the hooks provide a loading state, error,signIn method and user state


 import { useSignInWithGoogle } from "react-firebase-hooks/auth";
const [signInWithGoogle, user, loading, error] = useSignInWithGoogle(auth);

Conclusion

This is also all we need to deploy firebase to our app.

This is all to creating a note authenticated app. The full repo is here and also the hosted project is here. I will be glad to get your feedback and difficulties from this course in the comments. Also, do one better, make a note app and show it off here.

Recently, there has been a trend of bad news in the tech industry which is leaving many people in doubt. I have cause to tell you not to fear, the only thing to consider is continous self improvement which I am here to help you with, the market always adjust itself and this is one of those days, people are still getting good jobs now.