Learn Full-Stack Web Development with React and GraphQL by Building a Sticky Note App - Part three
Tags: apolloclient, react, typescript, javascript
Published: 2023-02-20 17:52:49

---
title: Learn Full-Stack Web Development with React and GraphQL by Building a Sticky Note App - Part three
published: true
date: 2023-01-09 16:47:18 UTC
tags: ApolloClient, React, TypeScript, JavaScript
canonical_url: https://kingdavvid.hashnode.dev/learn-full-stack-web-development-with-react-and-graphql-by-building-a-sticky-note-app-part-three
---

# Introduction

Hello again!👋 In the previous part of the series, we covered how to build the backend of the Sticky Note app using GraphQL, Apollo Server 4, Nexus and MongoDB. In this section, we will be integrating our backend API with the frontend and enabling communication between the two. This will allow the frontend to send requests to and receive data from our GraphQL API.

# Prerequisites

We will be using the frontend code that we built in the first part of this series to connect to the GraphQL API that we created in the second part. It is important that you check out both of these sections before proceeding, as the knowledge gained in those parts will be essential for completing this section.

# Setting the Project

To get started with this section, we will first need to clone the [GitHub repository](https://github.com/tope-olajide/sticky-note-react) that contains the code from the first part of this tutorial. To do this, open your terminal and run the following command:

```
 git clone https://github.com/tope-olajide/sticky-note-react.git

```

This will create a local copy of the repository on your machine. Once the cloning process is complete, you can navigate to the repository directory by running:

```
bash cd sticky-note-react

```

To install all of the necessary dependencies for this project, run the following command in your terminal:

```
yarn install

```

# What is Apollo Client?

Apollo Client is a comprehensive state management library for JavaScript that enables you to manage both local and remote data with GraphQL. It is designed to help you easily connect to a GraphQL server and retrieve data from it, as well as update and manipulate that data using GraphQL mutations. Apollo Client features automatic caching, reliable change detection, and a simple, declarative API that makes it easy to work with GraphQL in your JavaScript applications.

# Installing the Apollo Client and GraphQL

Run the following command to install Apollo Client and GraphQL

```
yarn add @apollo/client graphql

```

# Setting up the `InMemoryCache`

Apollo GraphQL's `InMemoryCache` is a powerful and flexible caching library that is designed to work with GraphQL servers. It is an in-memory data store that stores the results of GraphQL queries, mutations, and subscriptions in a cache.

Create a new file called `cache.ts` in the root directory, then open it and enter the following code:

```
import { InMemoryCache } from '@apollo/client';

export const cache: InMemoryCache = new InMemoryCache({ })

```

This will create an instance of `InMemoryCache`. We will use this cache instance to store the data that will be fetched from our GraphQL server. In addition, we will use the `InMemoryCache` instead of React `useState` hook to store local data that we want to manage within our application.

## Initializing the Apollo Client

To initialize the Apollo Client in our project, let's create a new file called `client.ts` in the root directory of our project and add the following code to it:

```

import { ApolloClient, createHttpLink } from '@apollo/client';
import { cache } from './cache';

const link = createHttpLink({
    uri: 'http://localhost:4000/graphql',
    credentials: 'include'
});
const client = new ApolloClient({
    cache,
    link,
});

export default client

```

The `createHttpLink` object will handle the communication with our GraphQL server. We passed in two properties, the `uri` and `credentials`. The `credential` options provide the credential policy that will be applied to each API call, and the `uri` options contain the URL of our GraphQL server.

We set the value of the `credentials` option to `include`. If the front-end and back-end have different domain names, you should set the `credentials` option to `include`. If they have the same domain name, you should use `same-origin` instead.

in the following snippets:

```
const client = new ApolloClient({
    cache,
    link,
});

```

We created an instance of the `ApolloClient` object by passing in the `cache` and `link` objects as arguments.

# Making the `ApolloClient` instance available to all the React components in our App.

To make the `client` object we created in the `client.ts` file available for use in all our components, we will wrap `ApolloProvider` to the root component of our app. `ApolloProvider` is a higher-order component (HOC) from the `react-apollo` library that helps to provide our React components with the data they need from a GraphQL server.

We will use the `ApolloProvider` to provide the `client` object to all of the components in the app.

Overwrite everything in `pages/_app.tsx` file with the following code:

```
import '../styles/globals.css'
import { AppProps } from 'next/app'
import { ApolloProvider } from '@apollo/client'
import client from '../client'
import { useEffect, useState } from 'react';
function MyApp({ Component, pageProps }: AppProps) {
  const [isClient, setIsClient] = useState(false)
  useEffect(() => {
    if (typeof window !== "undefined") {
      setIsClient(true)
    }
  }, [])
  if (isClient) {
    return (
      <ApolloProvider client={client}>
        <Component {...pageProps} />
      </ApolloProvider>
    )
  }
return(
  <>
  <h1>Loading...</h1>
  </>
)
}
export default MyApp

```

We are using Next.js to build the front end of our app, which means that the content of our web app will be generated on the server side and sent to the client as a fully rendered HTML page. Later in this tutorial, we will need to access the browser's local storage to store the login status of our users. While the contents of the web app are being generated on the server, the localStorage will not be available for use, thereby causing errors in our app. To fix that, we will only render all our components only when they are available on the client side.

So we created a new state called `isClient` and initialized it with `false` boolean value. In the `useEffect` hook, we checked if the **window object** was not `undefined`. The **window** object is only present on the client-side, once it is not undefined, we will change the `isClient` boolean value to `true`.

In the snippets below:

```
 if (isClient) {
    return (
      <ApolloProvider client={client}>
        <Component {...pageProps} />
      </ApolloProvider>
    )
  }
return(
  <>
  <h1>Loading...</h1>
  </>
)

```

If the `isClient` boolean value is `true,` we will render the pages wrapped in the `ApolloProvider` component, which provides the `client` object to the rest of the app via React's context API, otherwise, we'll render a loading text.

# Updating the global styles

I made some changes to the CSS file used in this project. Open the `styles/global.css` file and overwrite everything in there with the following codes:

```
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #F8FAFE;
  overflow: auto;

}

.main-container {
 min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}
.card, .delete-note-modal{
  width: 300px;
  height: 350px;
  background-color: #f9e675;
   box-shadow: 0 8px 16px 0 rgba(0,0,0,0.4); 
  border: 1px solid rgb(0, 0, 0,.5);
  margin: 1rem;
}
.delete-note-modal {
  text-align: center;
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  }
  .delete-note-modal h3 {
    margin: 0 0 1rem 0;
  }

  .delete-note-modal button {
    width: 90%;
    background-color: rgb(31, 15, 15);
  }
.card-maximized {
  width: 100%;
  height: 100%;
  background-color: #f9e675;
  box-shadow: 0 8px 16px 0 rgba(0,0,0,0.4);
  border: 1px solid rgb(0, 0, 0,.5);
  position: fixed;
  z-index: 10;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  top: 0;
}
.card-header {
 height: 10%;
}
.card-body {
  background-color: rgba(253, 253, 253, 0.4);
  height: 80%;
}

.text-area {
width: 100%;
height: 100%;
background-color: transparent;
border: none;
outline: none;
color: black;
z-index: 5;
resize: none;
}

.text-container{
width: 90%;
height: 100%;
margin: 0 auto;
}

.card-footer {
  width: 100%;
  height: 10%;
  border-top: 1px solid rgba(0, 0, 0, 0.4);
}

.icon-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
  height: 100%;
}

.right-icon, .left-icon {
    display: flex;
}

.icon {
  margin: 3px;
  color: black;
  padding: 5px 7px;
  cursor: pointer;
  opacity: .8;
  font-size: 1.2rem;
}

.icon:hover {
  background-color: rgba(10, 10, 9, 0.2);
  opacity: 1;
}

.theme-color-container {
  width: 100%;
  height: 100%;
  display: flex;
  background-color: black;
}

.theme-color:hover {
  opacity: .9;
  cursor: pointer;
}

.yellow, .green, .pink, .purple, .blue, .gray, .charcoal {
  width: 14.3%;

}

.yellow, #yellow {
background-color: #f9e675;
}

.green, #green {
  background-color: #b2ed9e ;
}
.pink, #pink {
  background-color: #f2b3e4;
}
.purple, #purple {
  background-color: #ceb1ff;
}
.blue, #blue {
  background-color: #addcfe;
}
.gray, #gray {
  background-color: #dcdee2;
}
.charcoal, #charcoal {
  background-color: #77797e;
}
.notes-container {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
align-items: flex-start;
}

.no-notes-container, .error-page-container{
  display: flex;
  flex-direction: column;
  width: 350px;
  margin: 3rem auto;
  align-items: center;
  border: 1px solid rgba(0, 0, 0, 0.2);
  padding: 3rem 1rem;
  box-shadow: 0 2px 4px 0 rgba(0,0,0,0.4);
  background-color: #f9e675;
  color: black;
  border-radius: 5px;
  text-align: center;
}
.no-notes-container button, .error-page-container button {
 background-color: #000000;
 border-radius: 5px;
}
.auth-container{
width: 350px;
margin: 2rem auto;
padding: 2rem;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.4);
background-color: white;
}

.auth-form{
flex-wrap: wrap;
display: flex;
width:320px;
margin: 0 auto;
justify-content: center;
}

input {
border: 1px solid black;
margin: .5rem;
padding: 1rem;
font-size: 1rem;
width:320px;
outline: none;
}

.auth-navbar{
  background-color: rgb(34, 32, 32);
  padding: 1rem;box-shadow: 0 2px 4px 0 rgba(0,0,0,0.4);
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.auth-navbar h1{
  margin: 0;
  font-size: 1.3rem;
  padding-left: 1rem;
  color: white;
}
.auth-navbar h5{
  margin: 0;

}
.auth-navbar .menu {
  display: flex;
}

.auth-navbar .menu a{
  margin: 0;
  color: white;
  padding: 0 1rem;
  text-transform: uppercase;
  text-decoration: none;
}

.auth-navbar .menu a:hover {
  opacity: .8;
  cursor: pointer;
}

.welcome-text h2{
  text-align: center;
  text-transform: uppercase;
  margin: 2rem 0 0rem 0;
}

button {
  border: none;
  margin: .5rem;
  padding: 1rem;
  font-size: 1rem;
  width:320px;
  outline: none;
  background-color: blue;
  color: white;
  cursor: pointer;
}

button:hover {
  opacity: .7;
}
.logout-button{
  width: 80px;
  padding: .5rem;
  border-radius: 3px;
  margin: .1rem;
  font-size: .9rem;
}
.show-error{
  display: flex;
  flex-direction: column;
  align-items: center;
}
.show-error p {
  color: red;
  margin: 0;
  font-weight: 600;
}
.hide-error{
  display: none;
}
.error-icon {
margin:0;
color: red;
padding: .4rem;
}

.snackbar-container {
  min-width: 250px;
  background-color: rgb(29, 29, 32);
  color: #fff;
  text-align: center;
  border-radius: 2px;
  position: fixed;
  z-index: 1;
  right: 0%;
  bottom: 30px;
  font-size: 17px;
  padding: 0;
  margin-right: 30px;
  box-shadow: 0 8px 16px 0 rgba(0,0,0,0.4);
}
.hide{
  display: none;
}

 @media screen and (max-width: 468px) {
  .auth-container{
    width: 90%;
    padding: 2.5rem 0;
    }
    .auth-form {
      width:90%;
      }
      .auth-navbar h1{
        font-size: 1rem;
        padding-left: 0.5rem;
      }
      .auth-navbar .menu h5{
        font-size: .7rem;
      }
      .no-notes-container, .error-page-container{
        width: 80%;
      }
      .no-notes-container button, .error-page-container button {
        width: 80%;
       }
}

@media screen and (max-width: 280px) {
  .auth-container {
    width: 100%;
    }
} 

footer {
width: 100%;
left: 0;
bottom: 0;
background-color: black;
text-align: center;
color: white;
margin-top: 5rem;
}

footer p {
  padding: 2rem 0;
}
.loading-container{
  width: 100vw;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.loader{
  border: 5px solid #ffd900;
  border-top: 5px solid #000000;
  border-radius: 50%;
  width: 50px;
  height: 50px;
  animation: spin 2s linear infinite;
  margin: 0px;
  padding: 0;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

```

# Creating the `AuthNavigationBar` Component

The `AuthNavigationBar` component is the navigation bar that will be displayed on the sign up and login page. In the `components` directory, create a new file called `AuthNavigationBar.tsx` and type the following code into it:

```
import Link from 'next/link'

const AuthNavigationbar = () => {
    return (
        <>
        <div className="auth-navbar">
        <h1>Sticky Note</h1>
        <div className="menu">
        <h5><Link href="/sign-up"> Sign Up </Link></h5>
        <h5><Link href="/sign-in"> Log In </Link></h5>
        </div>
        </div>
        </>
    )
}
export default AuthNavigationbar

```

We added two links to the navigation bar: `Sign Up` and `Log In`. We will create their corresponding pages very soon.

# Creating the `MainNavigationBar` Component

The `MainNavigationBar` component is the navigation bar that will be rendered on the main page after the user has been authenticated.

To create the `MainNavigationBar` component, locate `components` directory, then create a new file called `MainNavigationBar.tsx` and add the following code:

```
const MainNavigationBar = () => {
    return (
        <>
            <div className="auth-navbar">
                <h1>Sticky Note</h1>
                <div className="menu">
                    <button className="logout-button">Logout</button>
                </div>
            </div>
        </>
    )
}
export default MainNavigationBar

```

The `MainNavigationBar` component has a logout button that does nothing for now. We will implement the logout feature toward the end of this tutorial.

# Creating the `ErrorPage` Component

When a user encounters an error on the website, the `ErrorPage` component will be used to display an error message to the user.

In the `components` directory, create a new file called `ErrorPage.tsx` and type in the following code to it:

```
const ErrorPage = () => {
    return (
        <>
        <section className="error-page-container">
        <h3>An Error occured.</h3>
        <button onClick={()=>window.location.reload()}> Reload Page </button>
        </section>
        </>
    )
}

export default ErrorPage

```

# Creating the `Loading` Component

Create a new file called `Loading.tsx` in the `components` directory, and add the following code to it:

```
const Loading = () => {
    return (
        <>
            <section className="loading-container">
                <div className="loader"></div>
            </section>
        </>
    )
}

export default Loading;

```

The loading component will be displayed whenever the application is in the process of fetching all of the user's sticky notes.

# Creating the `user` and `note` Mutations

In GraphQL, a mutation is a type of operation that allows you to create, update, or delete data on the server. Mutations are used to modify data in a database or other persistent storage. In this part, we will create the mutations for `user` and `note`.

Before implementing those mutations, let's install `react-toastify` by running the following command:

```
yarn add react-toastify

```

The `react-toastify` library will be used to display notifications to the user. Once the installation is complete, open the `pages/_app.tsx` file and import the `react-toastify` css file:

```
import "react-toastify/dist/ReactToastify.css";

```

## Creating the `user` Mutation

At the root directory, create a new folder called `mutations`. Open it up and create a new file called `users.ts`. In the `user.ts` file, add the following mutations to it:

```
import { gql } from '@apollo/client';

export const SIGNUP_MUTATION = gql`
  mutation SignUp(
    $fullname: String!
    $email: String!
    $password: String!
    $username: String!
  ) {
    signupUser(
      data: {
        fullname: $fullname
        email: $email
        password: $password
        username: $username
      }
    ) {
      user{
        username
      }
    }
  }
`;

export const LOGIN_MUTATION = gql`
  mutation signin($usernameOrEmail: String!, $password: String!) {
    signinUser(data: { usernameOrEmail: $usernameOrEmail, password: $password }) {
      signupUser{
        username
      }
    }
  }
`;

export const LOGOUT_MUTATION = gql`
mutation signoutUser {
  signoutUser
}
`

```

We created three GraphQL mutations: `SIGNUP_MUTATION`, `LOGIN_MUTATION`, and `LOGOUT_MUTATION`.

These mutations can be used to create a new user account, log in an existing user, and log out the current user, respectively.

The `SIGNUP_MUTATION` mutation will be used to create a new user account with the given `fullname`, `email`, `password`, and `username` data. It takes these four variables as input and passes them to the `signupUser` mutation on the server, which creates a new user account with the provided data. The mutation returns the `username` of the newly created user.

The `LOGIN_MUTATION` mutation will be used to log in an existing user with the given `usernameOrEmail` and `password`. It passes these variables to the `signinUser` mutation on the server, which authenticates the user and returns the `username` of the logged in user. We can actually return more data if there's any need for them. The `username` was only returned here because it will be used to welcome the user after registration or sign-in.

The `LOGOUT_MUTATION` mutation is used to log out the current user. It calls the `signoutUser` mutation on the server, which logs out the user and removes the user's session.

## Creating the `note` Mutation

Let's create another mutation for the notes. Inside the `mutation` directory, create another file named `note.ts`. Open it up and add the following mutations to it:

```
import { gql } from '@apollo/client';

export const SAVE_NOTE = gql`
mutation newNote ($data: NoteData!) {
    newNote (data: $data) {
        color
        content
        id
    }
}
`
export const MODIFY_NOTE = gql `
mutation modifyNote ($data: NoteData!, $noteId: String!) {
    modifyNote (data: $data, noteId: $noteId) {
        color
        content
        id
    }
}
`
export const DELETE_NOTE = gql `
mutation deleteNote ($noteId: String!) {
    deleteNote (noteId: $noteId) {
        color
        content
        id
    }
}
`

```

We created three new mutations namely: `SAVE_NOTE`, `MODIFY_NOTE`, and `DELETE_NOTE`.

These mutations can be used to create a new sticky note, modify an existing sticky note, and delete an existing sticky note, respectively.

The `SAVE_NOTE` mutation will be used to create a new sticky note with the given `data`. It takes a single variable, `$data`, as input and passes it to the `newNote` mutation on the server, which creates a new sticky note with the provided data. The mutation returns the `color`, `content`, and `id` of the newly created sticky note.

The `MODIFY_NOTE` mutation will be used to modify an existing sticky note with the given `data` and `noteId`. It takes these two variables as input and passes them to the `modifyNote` mutation on the server, which modifies the sticky note with the provided data. The mutation returns the `color`, `content`, and `id` of the modified sticky note.

The `DELETE_NOTE` mutation will used to delete an existing sticky note with the given `noteId`. It passes the `noteId` variable to the `deleteNote` mutation on the server, which deletes the sticky note with the provided `noteId`. The mutation returns the `color`, `content`, and `id` of the deleted sticky note.

# Creating the `note` Queries

Next, we will create a query that will be used to fetch all the notes. Create another folder at the root directory of your project called `queries`. After that, you will create a new file inside the folder named `note.ts`. Open the file and add the following codes:

```

import { gql } from '@apollo/client';

export const FETCH_ALL_NOTES = gql`
query allNotes {
    allNotes {
        color
        content
        id
        isSaved @client
        isMaximized @client
        isSaving @client
        isError @client
        isDeleteNoteConfirmationVisible @client
    }
}
`

```

We created a new GraphQL query called `FETCH_ALL_NOTES` that will be used to fetch all of the sticky notes in the application.

The `FETCH_ALL_NOTES` query returns a list of all the sticky notes in the application, including their `color`, `content`, and `id`. It also includes several fields that are marked with the `@client` directive, indicating that these fields are stored on the client and not returned by the server. The `isSaved`, `isMaximized`, `isSaving`, `isError`, and `isDeleteNoteConfirmationVisible` fields are all client-side only fields that are used to store the state of the sticky notes in the client. We will be defining all these local-only fields in a moment,

When the `FETCH_ALL_NOTES` query is executed, it will request a list of all the sticky notes from the server and return the `color`, `content`, and `id` of each sticky note in the list. It will also include the client-side fields for each sticky note in the returned data.

# Defining the local-only fields

In Apollo Client, a **local-only field** is a field that is defined in the client-side schema, but not in the server-side schema. This means that the field is not fetched from the server, but rather is used to store local data that is specific to the client. We'll be using these local-only fields to store and manage our local data instead of React state.

To define the local-only fields, open the `cache.ts` file and update it with the following codes:

```
import { InMemoryCache } from '@apollo/client';

export const cache: InMemoryCache = new InMemoryCache({
    typePolicies: {
       Note: {
         fields: {
           isMaximized:{
             read(isMaximized = false) {
               return isMaximized ;
             },
           },
           isDeleteNoteConfirmationVisible:{
            read(isDeleteNoteConfirmationVisible = false) {
              return isDeleteNoteConfirmationVisible ;
            },
          },
           isSaving:{
             read(isSaving = false) {
               return isSaving ;
             }
           },
            isError:{
             read(isError = false) {
               return isError ;
             }
           },
           isSaved:{
            read(isSaved = true) {
              return isSaved ;
            }
          } 
         }
       }
     }
})

```

We used the `typePolicies` property of the `InMemoryCache` object to define local-only fields. The `typePolicies` property is an object that maps GraphQL type names to policy objects, which define the behavior for that type.

In this case, the `typePolicies` object includes a `Note` field that defines custom caching behavior for `Note` data in the cache.

The `Note` type policy includes fields for `isMaximized`, `isDeleteNoteConfirmationVisible`, `isSaving`, `isError`, and `isSaved`. Each of these fields has a [`read` function](https://www.apollographql.com/docs/react/caching/cache-field-behavior/#the-read-function) that specifies the default value for the field when it is not present in the cache. For example, the `isMaximized` field has a default value of `false`, and the `isSaved` field has a default value of `true`.

The `read` functions for these fields are called whenever a field is accessed in the cache. If the field is present in the cache, the value stored in the cache will be returned. If the field is not present in the cache, the default value specified in the `read` function will be returned instead.

## Creating the `SignUp` Component

In the part, we will create a new component that will be used to register a new user. Navigate to the `components` directory and create a new file called `SignUp.tsx`. Open it up, then add the following code:

```
import { useState, ChangeEvent, FormEvent, useEffect } from 'react'
import Footer from './Footer'
import AuthNavigationbar from './AuthNavigationBar';
import { useMutation } from '@apollo/client';
import { ToastContainer, toast } from "react-toastify";
import { SIGNUP_MUTATION } from '../mutations/user';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { useRouter } from 'next/router'
const SignUp = () => {
const [signup, { loading, data, error }] = useMutation(SIGNUP_MUTATION);
const router = useRouter();
  const [userInputs, setUserInputs] = useState({})
  useEffect(() => {
     if (data) {
       setTimeout(() => router.push('/'), 3000);
       localStorage.setItem("isLoggedIn", 'true');
       const {username} = data.signupUser.user
      toast.success(`Welcome, ${username}!`, {
        position: "bottom-center"
      });
    }
    if (error) {
      toast.error('' + error, {
        position: "bottom-center",
        autoClose: false
      });
    }
 },[data, error, router]);
  const saveUserInputs = (event: ChangeEvent<HTMLInputElement>) => {
    setUserInputs({ ...userInputs, [event.target.name]: event.target.value });
  }

  const handleFormSubmit = async (event: FormEvent) => {
    event.preventDefault()
    await signup({
      variables: userInputs
    });
  };

  return (
    <>
      <section className='main-container'>
        <AuthNavigationbar />
        <ToastContainer />
        <div className="auth-container">
          <form className="auth-form">
            <h3>Create Acount</h3>
            <input type='text' name="fullname" placeholder="fullname" onChange={(event) => saveUserInputs(event)} minLength={4} />
            <input type='text' name="username" placeholder="username" onChange={(event) => saveUserInputs(event)} minLength={3} />
            <input type='email' name="email" placeholder="email" onChange={(event) => saveUserInputs(event)} required />
            <input type='password' name="password" placeholder="password" onChange={(event) => saveUserInputs(event)} minLength={5} />
            <button disabled={loading} onClick={(event) => handleFormSubmit(event)}>{loading?<FontAwesomeIcon icon={faSpinner} spin size="1x"/>:"Create Account"}</button>
          </form>
        </div>
        <Footer />
      </section>
    </>

  )
}
export default SignUp

```

We created a new component called `SignUp` that contains a form that will be used to collect the following user's data: `fullname`, `username`, `email`, and `password`. We also render the `AuthNavigationbar`, `ToastContainer` and `Footer` components inside it. The `ToastContainer` will be used to display some notifications to the user.

Once a user clicks on the `Create Account` button, the `handleFormSubmit()` function will be invoked. The `handleFormSubmit()` function will execute the `signup` mutation function which takes in the `userInputs` object as arguments.

In the following snippet:

```
const [signup, { loading, data, error }] = useMutation(SIGNUP_MUTATION);

```

We used the `useMutation` hook to execute the `SIGNUP_MUTATION`. The `useMutation` hook returns an object with several properties such as `loading`, `data`, `error`, etc and a function that can be used to execute the mutation, we named this function `signup`.

In the code below, we disabled the button and then displayed the `spinner` icon instead of the `Create Account` text on it when the `loading` value is [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy#).

```
<button disabled={loading} onClick={(event) => handleFormSubmit(event)}>{loading?<FontAwesomeIcon icon={faSpinner} spin size="1x"/>:"Create Account"}</button>

```

In the code below, we used the `react-toastify` library to display an `error` notification message if an error is encountered while running the `signup` mutation:

```
if (error) { toast.error('' + error, { position: "bottom-center", autoClose: false }); }

```

In the following snippets:

```
if (data) { setTimeout(() => router.push('/'), 3000); localStorage.setItem("isLoggedIn", 'true'); 
const {username} = data.signupUser.user;
toast.success(`Welcome, ${username}!`, { position: "bottom-center" }); 
}

```

If the `data` returns a [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy#) value, that means the mutation was successfully executed. We used the `react-toastify` library to welcome the user using the `username` returned from the execution of the `SIGNUP_MUTATION` query and redirect the user to the homepage after three seconds.

We saved the user's login status (`isLoggedIn`) to the localStorage. We will update the `_app.tsx` file to redirect the user straight to the homepage the next time they visit the website if the `isLoggedIn` status of the user is true. We will also create a `logout()` function that will change the `isLoggedIn` status to false once the user signs out. If the user's token has expired and the `isLoggedIn` value is `true`, we will redirect the user back to the login page and change the `isLoggedIn` value to `false`.

### Creating the `SignUp` Page

To create the `SignUp` page in our Next.js app, we need to create a new file in the `pages` directory of our project called `sign-up.tsx`. Open the `sign-up.tsx` file, then import and render the `SignUp` component in it like so:

```
import SignUp from "../components/SignUp"
const SignUpPage = () => {
  return (
    <>
      <SignUp />
    </>

  )
}
export default SignUpPage

```

When the `sign-up.tsx` page is rendered by Next.js, the `SignUpPage` component will be rendered to the page, which will in turn render the `SignUp` component.

Now you can navigate to `http://localhost:3000/sign-up` on your browser to view the signup page.

## Creating the `SignIn` Component

Inside the `components` directory, create a new file named `SignIn.tsx` and add the following code to it:

```
import { useMutation } from '@apollo/client'
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import router from 'next/router'
import { useState, ChangeEvent, FormEvent, useEffect } from 'react'
import { toast, ToastContainer } from 'react-toastify'
import { LOGIN_MUTATION } from '../mutations/user'
import AuthNavigationBar from './AuthNavigationBar'
import Footer from './Footer'

const SignIn = () => {
  const [userInputs, setUserInputs] = useState({})
  const [login, { loading, data, error }] = useMutation(LOGIN_MUTATION);
  useEffect(() => {
    if (error) {
      toast.error('' + error, {
        position: "bottom-center",
        autoClose: false
      });
    }
    if (data) {
      console.log(data)
      const {username} = data.signinUser.user
      localStorage.setItem("isLoggedIn", 'true');
      setTimeout(() => router.push('/'), 3000);
      toast.success(`Welcome back, ${username}`, {
        position: "bottom-center"
      });
    }
  }, [data, error]);
  const saveUserInputs = (event: ChangeEvent<HTMLInputElement>) => {
    setUserInputs({ ...userInputs, [event.target.name]: event.target.value })
  }

  const handleFormSubmit = async (event: FormEvent) => {
    event.preventDefault()
    await login({
      variables: userInputs
    });
  };

  return (
    <>
      <section className='main-container'>
        <AuthNavigationBar />
        <ToastContainer />
        <div className="auth-container">
          <form className="auth-form">
            <h3>Sign In</h3>
            <input type='text' name="usernameOrEmail" placeholder="Username or Email" onChange={(event) => saveUserInputs(event)} required />
            <input type='password' name="password" placeholder="Password" onChange={(e) => saveUserInputs(e)} required />
            <button disabled={loading} onClick={(event) => handleFormSubmit(event)}> {loading ? <FontAwesomeIcon icon={faSpinner} spin size="1x" /> : "Login"}</button>
          </form>
        </div>
        <Footer />
      </section>
    </>
  )
}
export default SignIn

```

First, we created a new mutate function called `login` that will be used to execute our `LOGIN_MUTATION` anytime it is invoked. Then we rendered an input form that will be used to collect the user's `usernameOrEmail` and `password` data. After that, we created a function named `handleFormSubmit`. The `handleFormSubmit` will call the `login` function any time it is invoked. We will call the `handleFormSubmit` function any time the `Login` button is clicked. The `useMutation` hooks returns the following properties: `loading`, `error`, and `data` that will be used to track the **mutate function** (`login`) state. Just like we did in the `SignUp` component, if the `loading` status value is `truthy` we'll display the font awesome spinner `icon` on the button instead of the "Login" text.

If the `error` value is truthy, we'll display the error notification using the `react-toastify` library, and if the `data` value if `truthy`, a success notification will be displayed, welcoming the user again with the `username` returned from the `mutation` function. Then the user will be redirected to the homepage after three seconds.

### Creating the `sign-in` Page

Let's create a new file named `sign-in.ts` in the `page` directory, just like we did with the `sign-up` page. We will import and render the `SignIn` component in it like this:

```
import SignIn from "../components/SignIn"

const SignInPage = () => {

  return (
    <>
      <SignIn />
    </>
  )
}
export default SignInPage

```

When the `sign-in.tsx` page is rendered by Next.js, the `SignInPage` component will be rendered to the page, which will in turn render the `SignIn` component.

Now you can navigate to `http://localhost:3000/sign-in` on your browser to view the sign in page.

# Updating Home Page

Let's update the homepage to render the `NoteContainer` if the `isLoggedIn` value in the localStorage is true. But if the `isLoggedIn` value is false, then the user will be redirected to the login page. Navigate to `pages/index.ts` file and open it, and update it with the following:

```
import NoteContainer from "../components/NoteContainer";
import SignIn from "../components/SignIn";

function HomePage() {
  const isLoggedIn = localStorage.getItem("isLoggedIn");
    if(isLoggedIn === 'true'){
      return <NoteContainer />
    }
  return <SignIn />
}

export default HomePage

```

# Updating the `INoteCardProps` Interface

Update the `INoteCardProps` interface in the `typings/index.ts` file to include the following properties:

- `saveUserNote(id: string, color: Theme, contents: string, isSaved: boolean): Promise;`

- `deleteNote(noteId: string, isSaved:boolean): void;`

- `color:Theme;`

- `createNote(noteId: string): void;`

Everything should be looking like this now:

```
export interface INoteCardProps {
    createNote(noteId?: string): void;
    id:string;
    color:Theme;
    isMaximized:boolean;
    contents: string;
    isSaved: boolean;
    isSaving: boolean;
    isError: boolean;
    changeColor(id:string, color:Theme): void;
    toggleFullscreen(noteId:string):void;
    isDeleteNoteConfirmationVisible:boolean,
    toggleDeleteNoteConfirmationMessage(id:string):void;
    deleteNote(noteId: string, isSaved:boolean): void;
    saveUserNote(id: string, color: Theme, contents: string, isSaved: boolean): Promise<void>;
}

```

# Defining the `IAllNoteData` interfaces

Let's create a new file called:`IAllNoteData.` We will use this type to define our query result.

Let's open up the `typings/index.ts` file and create a interface called `IAllNoteData`:

```
  export interface IAllNoteData {
    allNotes:INote[] 
  }

```

The `IAllNoteData` represents data for a collection of notes. It has a single property, `allNotes`, which is an array of objects that implement the `INote` interface.

# Updating the `NoteCard` component

As discussed in the first part of this article, the `NoteCard` component is a child component to `NoteContainer` component. It will be used as a template to render all the sticky note that belongs to a user.

Before we proceed, let's first update the `NoteCard.tsx` file with the following code, which includes some changes to the original code:

```
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPlus, faWindowMaximize, faTrash, faSave, faExclamationTriangle, faSpinner, faCheck } from '@fortawesome/free-solid-svg-icons'
import { ChangeEvent, useState } from 'react';
import { INoteCardProps, Theme } from '../typings';

const NoteCard: React.FC<INoteCardProps> = (props) => {
const [noteContents, setNoteContents] = useState('');

const changeNoteColor = (id:string, color:Theme) => {
    props.changeColor(id, color);
    props.saveUserNote(id, color, noteContents||props.contents, props.isSaved)
}

    let timer:NodeJS.Timeout;
    const handleContentChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
        clearTimeout(timer)
        const newTimer: NodeJS.Timeout = setTimeout(() => {
            setNoteContents(event.target.value)
            props.saveUserNote(props.id, props.color, event.target.value, props.isSaved)
        }, 2000)
        timer = newTimer;
    }

    if (props.isDeleteNoteConfirmationVisible) {
        return (
            <section className="delete-note-modal" id={props.color}>
                <h3>This note will be permanently deleted, continue?</h3>
                <div>
                    <button onClick={() => props.deleteNote(props.id, props.isSaved)}> Yes </button>
                    <button onClick={() => props.toggleDeleteNoteConfirmationMessage(props.id)}> No</button>
                </div>
            </section>
        )
    }
    return (
        <>
            <div className={props.isMaximized ? "card-maximized" : "card"} id={props.color}>
                <div className="card-header">
                    <div className="icon-container">
                        <div className="left-icon">
                            <div className="icon" onClick={() => props.createNote(props.id)}>{props.isMaximized ?null:<FontAwesomeIcon icon={faPlus} />}</div>
                            <div className="icon" >{props.isSaving ? <FontAwesomeIcon icon={faSpinner} spin />:props.isError ? <FontAwesomeIcon icon={faExclamationTriangle} />:props.isSaved ? <FontAwesomeIcon icon={faCheck} /> : null}</div>
                        </div>
                        <div className="right-icon">
                            <div className="icon" onClick={() => props.toggleDeleteNoteConfirmationMessage(props.id)} >{props.isMaximized ?null:<FontAwesomeIcon icon={faTrash} /> }</div>
                            <div className="icon" onClick={()=>props.saveUserNote(props.id, props.color, noteContents||props.contents, props.isSaved)}><FontAwesomeIcon icon={faSave} /></div>
                            <div className="icon" onClick={() => props.toggleFullscreen(props.id)}><FontAwesomeIcon icon={faWindowMaximize} /></div>
                        </div>
                    </div>
                </div>
                <div className="card-body">
                    <div className="text-container">
                        <textarea defaultValue={props.contents} onChange={(e) => handleContentChange(e)} className="text-area"></textarea>
                    </div>
                </div>
                <div className="card-footer">
                    <div className="theme-color-container">
                        <div className="theme-color yellow" onClick={() => changeNoteColor(props.id, Theme.Yellow)} > </div>
                        <div className="theme-color green" onClick={() =>changeNoteColor(props.id, Theme.Green)}></div>
                        <div className="theme-color pink" onClick={() => changeNoteColor(props.id, Theme.Pink)}></div>
                        <div className="theme-color purple" onClick={() => changeNoteColor(props.id, Theme.Purple)}></div>
                        <div className="theme-color blue" onClick={() => changeNoteColor(props.id, Theme.Blue)}></div>
                        <div className="theme-color gray" onClick={() => changeNoteColor(props.id, Theme.Gray)}></div>
                        <div className="theme-color charcoal" onClick={() => changeNoteColor(props.id, Theme.Charcoal)}></div>
                    </div>
                </div>
            </div>
        </>
    )
}
export default NoteCard

```

## Breaking down the `NoteCard` Component

In the following snippets:

```
                <div className="card-body">
                    <div className="text-container">
                        <textarea defaultValue={props.contents}
                            onChange={(e) => handleContentChange(e)}
                            className="text-area"></textarea>
                    </div>
                </div>

```

We created the sticky note body, which is made up of the [HTML `<textarea>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea). Anytime there's a change in the Textarea, the `handleContentChange` function will be invoked.

The `setTimeout` method in the `handleContentChange()` function is used in the following snippets to automatically save a sticky note if the user stops typing after two seconds by calling the `saveUserNote` function:

```
   let timer:NodeJS.Timeout;
    const handleContentChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
        clearTimeout(timer)
        const newTimer: NodeJS.Timeout = setTimeout(() => {
            setNoteContents(event.target.value)
            props.saveUserNote(props.id, props.color, event.target.value, props.isSaved)
        }, 2000)
        timer = newTimer;
    }

```

If the user did not wait for 2 seconds before typing the next letter, the previous counter is cleared and another timer will start counting the moment the user starts typing again.

The following snippets will call the `changeColor` function in the `NoteContainer` component with the `id` and `color` of the selected sticky note:

```
const changeNoteColor = (id:string, color:Theme) => {
    props.changeColor(id, color);
    props.saveUserNote(id, color, noteContents||props.contents, props.isSaved)
}

```

The `changeNoteColor` function will be invoked any time the user clicks on any theme color on the sticky note:

```
changeNoteColor(props.id, Theme.Yellow)} >

changeNoteColor(props.id, Theme.Green)}>

 changeNoteColor(props.id, Theme.Pink)}>

 changeNoteColor(props.id, Theme.Purple)}>

 changeNoteColor(props.id, Theme.Blue)}>

 changeNoteColor(props.id, Theme.Gray)}>

 changeNoteColor(props.id, Theme.Charcoal)}>

```

"The following code was used to toggle the class of the element between `card-maximized` and `card` based on the value of the `isMaximized` boolean: "

```
 <div className={props.isMaximized ? "card-maximized" : "card"} id={props.color}>

```

In the following snippets, we render the `faPlus`, `faSpinner`,`faExclamationTriangle`, `faTrash`, `faSave`, `faWindowMaximize`, `faCheck` icons on the sticky notes:

```
 <div className="icon-container">
                        <div className="left-icon">
                            <div className="icon" onClick={() => props.createNote(props.id)}>{props.isMaximized ?null:<FontAwesomeIcon icon={faPlus} />}</div>
                            <div className="icon" >{props.isSaving ? <FontAwesomeIcon icon={faSpinner} spin />:props.isError ? <FontAwesomeIcon icon={faExclamationTriangle} />:props.isSaved ? <FontAwesomeIcon icon={faCheck} /> : null}</div>
                        </div>
                        <div className="right-icon">
                            <div className="icon" onClick={() => props.toggleDeleteNoteConfirmationMessage(props.id)} >{props.isMaximized ?null:<FontAwesomeIcon icon={faTrash} /> }</div>
                            <div className="icon" onClick={()=>props.saveUserNote(props.id, props.color, noteContents||props.contents, props.isSaved)}><FontAwesomeIcon icon={faSave} /></div>
                            <div className="icon" onClick={() => props.toggleFullscreen(props.id)}><FontAwesomeIcon icon={faWindowMaximize} /></div>
                        </div>
                    </div>

```

The `faSave` icon will be used to manually save a note once a user clicks on it, the icon will also be hidden whenever the sticky note is maximized.

When the application is in the process of saving a sticky note, an icon representing a spinning circle (`faSpinner`) will be shown to the user. If the note was saved successfully, the `faCheck` icon will be displayed instead of the the `faSpinner` icon. If there is an error while saving the note, the `faExclamationTriangle` will be displayed.

On the `right-icon` section, whenever the `faTrash` icon is clicked, the `toggleDeleteNoteConfirmationMessage` function will be invoked, we'll talk more about all the functions soon.

The `toggleFullscreen` function will be called with the `id` of the sticky note if a user clicks on the `faWindowMaximize` icon.

# Updating the `NoteContainer` Component

Let's update the `NoteContainer` Component to render the `NoteCard` with the latest changes.

Replace the contents of the `components/NoteContainer.tsx` file with the following code:

```
import NoteCard from "./NoteCard"
import MainNavigationBar from './MainNavigationBar';
import Footer from "./Footer";
import { FETCH_ALL_NOTES } from "../queries/note";
import { useMutation, useQuery } from "@apollo/client";
import client from "../client";
import { DELETE_NOTE, MODIFY_NOTE, SAVE_NOTE } from "../mutations/note";
import router from "next/router";
import Loading from "./Loading";
import ErrorPage from "./ErrorPage";
import { IAllNoteData, Theme } from "../typings";
import NoStickyNote from "./NoStickyNotes";
import { toast, ToastContainer } from 'react-toastify'

const NoteContainer = () => {
  const { loading, error, data } = useQuery<IAllNoteData>(FETCH_ALL_NOTES);
  const [saveNote] = useMutation(SAVE_NOTE);
  const [modifyNote] = useMutation(MODIFY_NOTE);
  const [deleteNoteMutation] = useMutation(DELETE_NOTE);

  const createNote = (currentNoteId?: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const noteCopy = [...noteData!.allNotes];
    const currentNoteIndex: number = noteCopy.findIndex((note) => note.id === currentNoteId);
    const emptyNote = {
      content: "",
      color: Theme.Yellow,
      id: `${Date.now()}`,
      isMaximized: false,
      isDeleteNoteConfirmationVisible: false,
      isSaved: false,
      isSaving: false,
      isError: true
    }
    noteCopy.splice(currentNoteIndex + 1, 0, emptyNote)
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: [...noteCopy],
      },
    });
  }

  const changeColor = (id: string, color: Theme) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNotes = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return {
          ...note, color: color
        }
      }
      return note
    });

    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNotes
      },
    });
  }

  const toggleFullscreen = (id: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return { ...note, isMaximized: !note.isMaximized }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }

  const toggleDeleteNoteConfirmationMessage = (noteId: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === noteId) {
        return { ...note, isDeleteNoteConfirmationVisible: !note.isDeleteNoteConfirmationVisible }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }

  const showSavingNoteIcon = (id: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return { ...note, isSaving: true }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }
  const hideSavingNoteIcon = (id: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return { ...note, isSaving: false }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }

  const showErrorIcon = (id: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return { ...note, isError: true }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }

  const hideErrorIcon = (id: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return { ...note, isError: false }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }
  const deleteNote = async (noteId: string, isSaved: boolean) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const filteredNotes = noteData!.allNotes.filter((note) => {
      return note.id !== noteId;
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: filteredNotes
      },
    });
    if (isSaved) {
      await deleteNoteMutation({ variables: { noteId } });
    }
  }
  const saveUserNote = async (id: string, color: Theme, contents: string, isSaved: boolean) => {
    try {
      if (isSaved) {
        showSavingNoteIcon(id);
        hideErrorIcon(id);
        await modifyNote({ variables: { data: { color, content: contents }, noteId: id } });
        hideSavingNoteIcon(id);
      }
      else {
        showSavingNoteIcon(id);
        hideErrorIcon(id);
        const savedNote = await saveNote({ variables: { data: { color, content: contents } } });
        hideSavingNoteIcon(id);
        const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
        const updatedNote = noteData!.allNotes.map((note) => {
          if (note.id === id) {
            return { ...note, isSaved: true, id: savedNote.data.newNote.id, content: savedNote.data.newNote.content }
          }
          return note
        })
        client.writeQuery({
          query: FETCH_ALL_NOTES,
          data: {
            allNotes: updatedNote
          },
        });
      }
    }
    catch (error: any) {
      hideSavingNoteIcon(id);
      showErrorIcon(id);
      if (error.networkError?.message === "Response not successful: Received status code 401") {
        toast.error('Your session has expired. Kindly login again', {
          position: "bottom-center"
        });
        localStorage.setItem("isLoggedIn", 'false');
        router.push('/sign-in')
      }

    }
  }

  if (loading) {
    return (
      <>
        <Loading />
      </>
    )
  }
  if (error) {
    if (error.networkError?.message === "Response not successful: Received status code 401") {
      toast.error('Your session has expired. Kindly login again', {
        position: "bottom-center"
      });
      localStorage.setItem("isLoggedIn", 'false');
      router.push('/sign-in')
    }
    return (
      <>
        <section className='main-container'>
          <MainNavigationBar />
          <ErrorPage />
          <Footer />
        </section>
      </>
    )
  }
  if (!data!.allNotes.length) {
    return (
      <>
        <section className='main-container'>
          <MainNavigationBar />
          <NoStickyNote
            createNote={() => createNote()}
          />
          <Footer />
        </section>
      </>
    )
  }
  return (<>
    <section className='main-container'>
      <MainNavigationBar />
      <ToastContainer />
      <section className="notes-container">
        {data!.allNotes.map((eachNote) => {
          return (
            <NoteCard
              createNote={createNote}
              key={eachNote.id}
              changeColor={changeColor}
              id={eachNote.id}
              color={eachNote.color}
              toggleFullscreen={toggleFullscreen}
              isMaximized={eachNote.isMaximized}
              deleteNote={deleteNote}
              toggleDeleteNoteConfirmationMessage={toggleDeleteNoteConfirmationMessage}
              isDeleteNoteConfirmationVisible={eachNote.isDeleteNoteConfirmationVisible}
              contents={eachNote.content}
              saveUserNote={saveUserNote}
              isSaved={eachNote.isSaved}
              isSaving={eachNote.isSaving}
              isError={eachNote.isError}
            />
          )
        })
        }
      </section>
      <Footer />
    </section>
  </>
  )
}
export default NoteContainer

```

## Breaking Down the `NoteContainer` component:

### Fetching and Rendering all the Sticky Notes

Upon successful login, the first action performed is to retrieve all of the user's notes.

The following query was used to retrieve all of the notes belonging to the user:

```
const { loading, error, data } = useQuery(FETCH_ALL_NOTES);

```

The `useQuery` hook returns an object with `loading`, `error`, and `data` fields. While the `allNotes` query is being fetched, the `Loading` component will be rendered using the following code:

```
  if (loading) {
    return (
      <>
        <Loading />
      </>
    )
  }

```

In the snippets below, if the `useQuery` hooks encounters an error while retrieving the user's note, we check to see if the error was caused by an expired token and then redirect them to the login page; otherwise, the ErrorPage component is rendered:

```
  if (error) {
    if (error.networkError?.message === "Response not successful: Received status code 401") {
      toast.error('Your session has expired. Kindly login again', {
        position: "bottom-center"
      });
      localStorage.setItem("isLoggedIn", 'false');
      router.push('/sign-in')
    }
    return (
      <>
        <section className='main-container'>
          <MainNavigationBar />
          <ErrorPage />
          <Footer />
        </section>
      </>
    )
  }

```

What if the data was fetched but was empty because the user had not added any sticky notes? We've already created a component called `NoStickyNote` that will be rendered to the screen. The `NoStickyNote` component will render a text that informs the user that they do not have any sticky notes and a button to create a new sticky note.

The following codes will be used to render the `NoStickyNote` component if the `data.allNotes` array is empty:

```
  if (!data!.allNotes.length) {
    return (
      <>
        <section className='main-container'>
          <MainNavigationBar />
          <NoStickyNote
            createNote={() => createNote()}
          />
          <Footer />
        </section>
      </>
    )
  }

```

Finally, the following snippet will be used to render all the user's notes:

```
  return (<>
    <section className='main-container'>
      <MainNavigationBar />
      <ToastContainer />
      <section className="notes-container">
        {data!.allNotes.map((eachNote) => {
          return (
            <NoteCard
              createNote={createNote}
              key={eachNote.id}
              changeColor={changeColor}
              id={eachNote.id}
              color={eachNote.color}
              toggleFullscreen={toggleFullscreen}
              isMaximized={eachNote.isMaximized}
              deleteNote={deleteNote}
              toggleDeleteNoteConfirmationMessage={toggleDeleteNoteConfirmationMessage}
              isDeleteNoteConfirmationVisible={eachNote.isDeleteNoteConfirmationVisible}
              contents={eachNote.content}
              saveUserNote={saveUserNote}
              isSaved={eachNote.isSaved}
              isSaving={eachNote.isSaving}
              isError={eachNote.isError}
            />
          )
        })
        }
      </section>
      <Footer />
    </section>
  </>
  )

```

We iterated through the `data.allNotes` array using the `map()` method, then used the `NoteCard` component to render the `eachNote` object in the `data.allNotes` array.

The `NoteCard` component is passed several props, including `createNote`, `changeColor`, `toggleFullscreen`, `deleteNote`, `toggleDeleteNoteConfirmationMessage`, `id`, `color`, `isMaximized`, `isDeleteNoteConfirmationVisible`, `contents`, `saveUserNote`, `isSaved`, `isSaving`, and `isError`. These props are used to pass data and functions to the `NoteCard` component that it can use to display and manipulate the sticky notes. A more detailed explanation of these props will be provided soon.

### Creating a new Sticky Note

To create a new sticky note, we will call the `createNote()` function in the `NoteContainer` component. The `createNote()` function has an optional parameter called `currentNoteId`. Every sticky note has a `plus (+) icon`, once a user click on the `icon` we want a new sticky note to be created right beside it. The `id` of the sticky note whose `plus icon` is clicked is the `currentNoteId`.

We made the `currentNoteId` parameter optional because sometimes the user does not have to click the `plus icon` to create a new note. For example, when a user does not have a sticky note, the `NoStickyNotes` component with a `Add New` button will be rendered to the screen. Once the `Add New` button is pressed we do not need the `currentNoteId` parameter because the sticky note that will be created will be the first sticky note.

In the `createNote()` function, we used the following line to read the cached data:

```
const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });

```

After running the `FETCH_ALL_NOTES` query to fetch all the user's notes, the result was stored into the `cache` we created earlier. We used the `client.readQuery` to read the current data of the `FETCH_ALL_NOTES` GraphQL and store it into the `noteData` variable. The type of `noteData` is inferred to be `IAllNoteData` from the type argument passed to `readQuery`.

In the following line:

```
const noteCopy = [...noteData.allNotes];

```

We used the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#spread_in_array_literals) to copy the `noteData` and made modifications to the `noteCopy` without touching the original result. Modifying the `noteData` directly could lead to unexpected behavior in our application.

We used the following code to get the index of the sticky note that the user clicks on:

```
const currentNoteIndex: number = noteCopy.findIndex((note) => note.id === currentNoteId);

```

Then we created a new json object called: `emptyNote`:

```
    const emptyNote = {
      content: "",
      color: Theme.Yellow,
      id: `${Date.now()}`,
      isMaximized: false,
      isDeleteNoteConfirmationVisible: false,
      isSaved: false,
      isSaving: false,
      isError: true
    }

```

This object will be used to initialize a new note.

We used the following code to add a new note right beside the sticky note whose `plus` icon was clicked on:

```
noteCopy.splice(currentNoteIndex + 1, 0, emptyNote)

```

Then we used the `client.writeQuery` to update the `cache` with the modified `noteCopy` data:

```
client.writeQuery({ query: FETCH_ALL_NOTES, data: { allNotes: [...noteCopy], }, });

```

### Saving and Modifying the Sticky Notes

Saving and modify the sticky notes is not a straight forward process. First, we have to check if the note has been saved before so that we can call the `MODIFY_NOTE` mutation on it, but if the note has not been saved before, we'll make a request to the `SAVE_NOTE` mutation instead.

Two types of savings were also implemented here:

- auto save and

- manual save.

The sticky note will be automatically saved two seconds after a user stops typing on it, and a user can also manually save the note by clicking the `save` icon.

To save the note automatically or manually, we will call the `saveUserNote()` function.

The `saveUserNote()` function takes in the following parameter:

- `id` : the `id` of the sticky note to be saved

- `color`: the `color` of the note

- `contents`: the note's content,

- `isSaved`: the `isSaved` boolean will be used to check if the note has been saved or not.

If the note has already been saved, that means we just have to modify it, which is exactly what the snippets below does:

```
      if (isSaved) {
        showSavingNoteIcon(id);
        hideErrorIcon(id);
        await modifyNote({ variables: { data: { color, content: contents }, noteId: id } });
        hideSavingNoteIcon(id);
      }

```

We used the following mutations to modify a note:

```
await modifyNote({ variables: { data: { color, content: contents }, noteId: id } });

```

While while the mutation is being executed, we displayed the loading icon by calling the `showSavingNoteIcon(id)` function.

We will call the `showErrorIcon(id)` to dislay the error icon on the note if an error occured while modifying the note or call the `hideErrorIcon(id)` function to clear the error if the error has been resolved.

We will hide the loading icon after the execution is complete by calling `hideSavingNoteIcon(id)` function.

If the sticky note has not been saved before, the following snippets will be executed instead:

```
       else {
        showSavingNoteIcon(id);
        hideErrorIcon(id);
        const savedNote = await saveNote({ variables: { data: { color, content: contents } } });
        hideSavingNoteIcon(id);
        const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
        const updatedNote = noteData!.allNotes.map((note) => {
          if (note.id === id) {
            return { ...note, isSaved: true, id: savedNote.data.newNote.id, content: savedNote.data.newNote.content }
          }
          return note
        })
        client.writeQuery({
          query: FETCH_ALL_NOTES,
          data: {
            allNotes: updatedNote
          },
        });
      }

```

We display the saving icon and hide the error icon while the `saveNote` mutation is being executed. Once the note has been successfully saved, we change the `id` of the note from the temporary `id` we were using before (i.e., the one generated by [date.Now](http://date.Now)()) to the permanent `id` generated from our GraphQL server. We also change the `isSaved` boolean to `true` and update the note content in the `cache` with the saved note `content` sent from the backend.

If there are any errors while the note is being saved of or modified the following code will be executed:

```
    catch (error: any) {
      hideSavingNoteIcon(id);
      showErrorIcon(id);
      if (error.networkError?.message === "Response not successful: Received status code 401") {
        toast.error('Your session has expired. Kindly login again', {
          position: "bottom-center"
        });
        localStorage.setItem("isLoggedIn", 'false');
        router.push('/sign-in')
      }
    }

```

If the error message says "_Response not successful: Received status code 401_" that means the user's session as expired. So we will log the user out and redirect the user to the login page, then hide the saving icon and display the error icon on the sticky note.

### Changing the color of the notes

We used the `changeColor()` function to switch between the theme colors of a note. the function takes in two parameters: the `id` of the note and the `color` that the user want.

We fetched all the sticky notes in the `cache` by running the following query first:

```
const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });

```

Then in the following code:

```
    const updatedNotes = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return {
          ...note, color: color
        }
      }
      return note
    });

```

We searched for the note that matched the note `id` provided by the user using the [map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method, then changed the color to the color selected by the user. The `updatedNotes` variable was then used to store the new array that was generated by the map function.

The following snippets uses the `writeQuery` method to update the `cache` with the latest changes:

```
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNotes
      },
    });

```

### Minimizing and Maximizing the sticky note

To minimize and maximize a sticky note, we'll invoke the `toggleFullscreen(id)` function. Here's what the `toggleFullscreen` function looks like:

```
  const toggleFullscreen = (id: string) => {
    const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const updatedNote = noteData!.allNotes.map((note) => {
      if (note.id === id) {
        return { ...note, isMaximized: !note.isMaximized }
      }
      return note
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: updatedNote
      },
    });
  }

```

The `toggleFullscreen` function takes in the `id` of the note that the user want to minimize or maximize then change its `isMaximized` boolean value to the opposite of the previous value. That is, if the current boolean value of `isMaximized` is `false` it changes it to `true` and vice versa. Then modifies the `cache` with the `updatedNote`.

### Deleting a Sticky Note

We called two functions to delete a sticky note, the `toggleDeleteNoteConfirmationMessage()` and `deleteNote()` function.

We used the `deleteNote()` function to permanently remove a user's note. But we just don't want to delete a note once the user presses the trash icon on the sticky note; we also want to confirm it first. That's why we called the `toggleDeleteNoteConfirmationMessage()` function first. The `toggleDeleteNoteConfirmationMessage()` will render a text asking the user to confirm their action by clicking on a `Yes` or `No` button. Once the user clicks on the `Yes` button, the `deleteNote()` function with the `id` of the note will be invoked, but if the user clicks on the `No` button, the confirmation message will be hidden.

The `deleteNote` function takes in two parameters, the `id` and `isSaved` value of the note. The following statement in the `deleteNote` function searches for a note that matches the `id` parameter in the cache and then deletes it:

```
        const noteData = client.readQuery<IAllNoteData>({ query: FETCH_ALL_NOTES });
    const filteredNotes = noteData!.allNotes.filter((note) => {
      return note.id !== noteId;
    })
    client.writeQuery({
      query: FETCH_ALL_NOTES,
      data: {
        allNotes: filteredNotes
      },
    });

```

This is okay if the note has not been saved into the database, and we just want to remove it from the `cache`, but if the note has been saved, we need to execute a mutation that will delete the user's note from the database, that is what the following snippets does:

```
    if (isSaved) {
      await deleteNoteMutation({ variables: { noteId } });
    }

```

The above statement will execute the query that will permanently delete the user's note from the database.

### Logging Out the User

To log the user out, we need to execute the `LOGOUT_MUTATION` , clear the user data and redirect to login page. Open the `MainNavigationBar.tsx` file in the components directory and update it with the following code:

```

import { LOGOUT_MUTATION } from "../mutations/users";
import { useMutation } from "@apollo/client";
import router from 'next/router';
import client from "../client";

const MainNavigationBar = () => {
    const [signout] = useMutation(LOGOUT_MUTATION);

    const logout = () => {
        localStorage.setItem("isLoggedIn", 'false');
        client.clearStore();
        client.cache.gc();
        router.push('/sign-in');
        signout();
    }
    return (
        <>
            <div className="auth-navbar">
                <h1>Sticky Note</h1>
                <div className="menu">
                    <button onClick={logout} className="logout-button">Logout</button>
                </div>
            </div>
        </>
    )
}
export default MainNavigationBar

```

We define a function called `logout.` The function will be invoked anytime the logout button is clicked. Here's a brief explanation of what the function does:

- sets the value of `isLoggedIn` key in the browser's local storage to `false`.

- uses the `clearStore` method to delete all the locally cached data.

- the `client.cache.gc()` triggers garbage collection in the cache, which frees up memory by removing any unused data from memory.

- redirect the user to the login page