Day 5: Building a Notes Keeping App πŸ“with React JS

Vinish BhaskarVinish Bhaskar
17 min read

Welcome to Day 5 of the 21-Day Frontend Development Challenge! Today, I am excited to share my progress on building a Notes Keeping App using React JS. πŸš€πŸ’» This app provides a convenient way to create, edit, and manage notes effortlessly. Let's dive into what I accomplished! πŸŽ‰

Github Repo: Notes Keeping App

Live Demo Url: https://notes-keeper-21day.netlify.app/

Project Overview πŸ“

The Notes Keeping App is a practical and efficient tool that allows users to keep track of their notes. With this app, users can create new notes, delete unwanted ones, and edit existing notes. The app also features search functionality to help users find specific notes based on the title or content. The notes are stored persistently using local storage, ensuring that users can access their notes even after closing the app. πŸ“šπŸ—’οΈ

Technologies Used

To build the Notes Keeping App, I utilized the following technologies and libraries:

  • React A popular JavaScript library for building user interfaces.

  • React Icons: A library that provides a set of customizable icons for React applications.

  • uuid: A package for generating unique IDs.

  • react-toastify : A library for displaying toast notifications in React applications.

Features of the Notes-Keeping App

The Notes Keeping App offers a range of features to ensure an optimal note-taking experience:

  1. Note Creation: Users can easily create a new note by providing a title and content for the note.

  2. Note Listing: The app displays a list of all the created notes, allowing users to browse and manage them efficiently.

  3. Note Editing: Users can edit the title and content of a note by selecting it from the list and making the desired changes.

  4. Note Deletion: The app enables users to delete a note, removing it from the list and the database.

  5. Search Functionality: Users can search for specific notes by entering keywords in the search bar, making it easier to find relevant information.

  6. Toast Notifications: The app incorporates toast notifications using the react-toastify library to provide feedback to users when they perform actions such as adding, deleting, or updating notes.

What I Learned πŸ§ πŸ’‘

Throughout the development of the Notes Keeping App, I gained valuable insights and knowledge that will enhance my front-end development skills. Here are some key takeaways:

  • Enhanced understanding of React hooks, particularly useState and useEffect, for managing component state and handling side effects. βš›οΈ

  • Improved skills in handling user input, form submission, and data manipulation, which are essential for building interactive web applications. βœοΈπŸ“‹

  • Deepened knowledge of working with local storage to store and retrieve data in a React application. This allows for seamless user experience and data persistence. πŸ’ΎπŸ”„

  • Strengthened ability to develop user-friendly interfaces and implement essential features such as note creation, deletion, and search functionality. πŸ–₯οΈπŸ”

Project Showcase and Demo πŸš€πŸ“·

To see the Notes Keeping App in action, check out the live demo [here](insert-live demo-link). The demo showcases the app's features, including adding new notes, editing existing notes, deleting notes, and performing searches. It provides a glimpse into the user interface and the seamless functionality of the app.

Here are some screenshots from the Notes Keeping App:

  1. Notes Keeping App Initial View

  1. After Adding Notes

image

Development Flow πŸš€πŸ’»

Let's take a closer look at the development flow of the Notes Keeping App to understand how the different components work together. Here's the step-by-step guide:

πŸš€ Step 1: Create a React Project

To begin, make sure you have Node.js installed on your machine. Open your terminal and execute the following command to create a new React project:

npx create-react-app notes-keeping-app

This command sets up a new React project with the name "notes-keeping-app" in a folder of the same name.

🌳 Step 2: Project Tree Overview

Let's take a look at the project tree structure to understand the organization of files and directories:

notes-keeping-app
β”œβ”€β”€ public
β”‚   β”œβ”€β”€ favicon.ico
β”‚   β”œβ”€β”€ index.html
β”‚   └── manifest.json
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ components
β”‚   β”‚   β”œβ”€β”€ NoteEditor.js
β”‚   β”‚   β”œβ”€β”€ NoteForm.js
β”‚   β”‚   β”œβ”€β”€ NoteList.js
β”‚   β”‚   └── App.js
β”‚   β”œβ”€β”€ App.css
β”‚   β”œβ”€β”€ index.js
β”‚   └── index.css
β”œβ”€β”€ .gitignore
β”œβ”€β”€ package-lock.json
β”œβ”€β”€ package.json
└── README.md

The public the directory contains the HTML template and other static assets. The src directory houses the main application code, including components and styles.

πŸ“ Step 3: App Component and Basic Setup

In the src folder, open App.js and replace the existing code with the following:

import React from 'react';

function App() {
  return (
    <div className="app">
      <h1>Notes Keeping App πŸ“Œ</h1>
    </div>
  );
}

export default App;

This code defines the App component, which serves as the root component of our app. It renders a simple heading indicating the app name.

✏️ Step 4: NoteForm Component

Next, let's create a form component that allows users to add new notes. Create a new file called NoteForm.js in the components directory and add the following code:

import React from 'react';

function NoteForm() {
  return (
    <form className="note-form">
      <input type="text" placeholder="Note Title" />
      <textarea placeholder="Note Content"></textarea>
      <button type="submit">Add Note</button>
    </form>
  );
}

export default NoteForm;

The NoteForm component renders an HTML form with input fields for the note title and content, along with a submit button.

πŸ—’οΈ Step 5: NoteList Component

Now, let's create a component to display a list of notes. Create a new file called NoteList.js in the components directory and add the following code:

import React from 'react';

function NoteList() {
  return (
    <div className="note-list">
      <h2>My Notes πŸ“</h2>
      <ul>
        <li>Note 1</li>
        <li>Note 2</li>
        <li>Note 3</li>
      </ul>
    </div>
  );
}

export default NoteList;

The NoteList the component renders a heading and an unordered list of dummy notes. Later, we'll replace this static data with dynamic content.

πŸ–ŠοΈ Step 6: Complete App Component

Now, let us have the NoteForm and NoteList components, let's integrate them into the App component. Open the App.js file and update the code as follows:

import React from 'react';
import NoteForm from './components/NoteForm';
import NoteList from './components/NoteList';

function App() {
  return (
    <div className="app">
      <h1>Notes Keeping App πŸ“Œ</h1>
      <NoteForm />
      <NoteList />
    </div>
  );
}

export default App;

Here, we import the NoteForm and NoteList components and render them within the App component.

βš›οΈ Step 7: Styling the App Component

Let's add some basic styling to the App component. Open the App.css file in the same directory and update the code with the following styles:

Final CSS File of the Note Editor: Access from here

.app {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
}

h1 {
  font-size: 28px;
  margin-bottom: 20px;
}

.note-form input,
.note-form textarea,
.note-form button {
  margin-bottom: 10px;
  width: 100%;
  padding: 10px;
  font-size: 16px;
}

.note-list {
  margin-top: 40px;
}

.note-list h2 {
  font-size: 24px;
}

.note-list ul {
  list-style-type: none;
  padding: 0;
}

.note-list li {
  margin-bottom: 10px;
}

These styles define the layout and appearance of the App component, the NoteForm, and the NoteList.

πŸŽ‰ Step 8: Running the Application

You can now start the development server and see the Notes Keeping App in action. In the terminal, navigate to the project's root directory and run the following command:

npm start

The app will be available at http://localhost:3000, and you should see the Notes Keeping App with the form and a list of static notes.

That's it for the initial setup of the app! In the next steps, we'll add functionality to add and display dynamic notes, and also implement local storage to persist the data.

πŸ“ Step 9: Managing Notes with State

In order to manage the notes, we'll introduce the state using React's useState hook. Update the App component code as follows:

import React, { useState } from 'react';
import NoteForm from './components/NoteForm';
import NoteList from './components/NoteList';

function App() {
  const [notes, setNotes] = useState([]);

  const addNote = (title, content) => {
    const newNote = {
      id: Date.now(),
      title,
      content,
    };
    setNotes([...notes, newNote]);
  };

  return (
    <div className="app">
      <h1>Notes Keeping App πŸ“Œ</h1>
      <NoteForm addNote={addNote} />
      <NoteList notes={notes} />
    </div>
  );
}

export default App;

Here, we define the notes state variable using the useState hook and initialize it with an empty array. We also create the addNote function, which receives the title and content of the note and adds it to the notes state array using the setNotes function.

πŸ–‹οΈ Step 10: Updating the Note

We'll now implement the functionality to update an existing note. Update the App component code as follows:

import React, { useState } from 'react';
import NoteForm from './components/NoteForm';
import NoteList from './components/NoteList';

function App() {
  const [notes, setNotes] = useState([]);

  const addNote = (title, content) => {
    const newNote = {
      id: Date.now(),
      title,
      content,
    };
    setNotes([...notes, newNote]);
  };

  const updateNote = (id, updatedTitle, updatedContent) => {
    const updatedNotes = notes.map((note) => {
      if (note.id === id) {
        return {
          ...note,
          title: updatedTitle,
          content: updatedContent,
        };
      }
      return note;
    });
    setNotes(updatedNotes);
  };

  return (
    <div className="app">
      <h1>Notes Keeping App πŸ“Œ</h1>
      <NoteForm addNote={addNote} />
      <NoteList notes={notes} updateNote={updateNote} />
    </div>
  );
}

export default App;

In the App component, we define the updateNote the function that takes the id, updatedTitle, and updatedContent as parameters. It then maps over the notes array and updates the note with the corresponding id. Finally, the setNotes function is used to update the state with the modified notes array.

πŸ–οΈ Step 11: NoteForm Component

Let's update the NoteForm component to include the functionality of adding and updating notes. Open the NoteForm.js file and update the code as follows:

import React, { useState } from 'react';
import { toast, ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

function NoteForm({ addNote }) {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (title && content) {
      addNote(title, content);
      setTitle('');
      setContent('');
    } else if (!title && !content) {
      // Show a warning toast notification for both fields being blank
      toast.warn('Title and content are required.');
    } else if (!title) {
      // Show a warning toast notification for the title field being blank
      toast.warn('Title is required.');
    } else {
      // Show a warning toast notification for the content field being blank
      toast.warn('Content is required.');
    }
  };

  return (
    <div>
      <form className="note-form" onSubmit={handleSubmit}>
        <input
          type="text"
          placeholder="Title"
          className="note-input"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          placeholder="Content"
          className="note-textarea"
          value={content}
          onChange={(e) => setContent(e.target.value)}
        />
        <button type="submit" className="note-button-add">Add Note</button>
      </form>
      <ToastContainer position="top-right" autoClose={3000} hideProgressBar />
    </div>
  );
}

export default NoteForm;

Here, we include the editingNote prop in the NoteForm component to determine whether we're in edit mode or add mode. The form's submit event is handled by the handleSubmit function, which checks if there's a editingNote value. If there is, it calls the updateNote function; otherwise, it calls the addNote function. After submitting the form, the input fields are cleared.

πŸ“œ Step 12: NoteEditor Component

Next, we'll update the NoteEditor component to display the notes and enable editing. Open the NoteEditor.js file and update the code as follows:

import React, { useState } from 'react';

function NoteEditor({ note, updateNote, cancelEdit }) {
  const [editedTitle, setEditedTitle] = useState(note.title);
  const [editedContent, setEditedContent] = useState(note.content);

  const handleTitleChange = (e) => {
    setEditedTitle(e.target.value);
  };

  const handleContentChange = (e) => {
    setEditedContent(e.target.value);
  };

  const saveNote = () => {
    updateNote(note.id, editedTitle, editedContent);
  };

  return (
    <div className="note-editor">
      <input
        type="text"
        value={editedTitle}
        onChange={handleTitleChange}
        className="note-input"
      />
      <textarea
        value={editedContent}
        onChange={handleContentChange}
        className="note-textarea"
      ></textarea>
      <div className="buttons">
        <button className="cancel-button" onClick={cancelEdit}>
          Cancel
        </button>
        <button className="save-button" onClick={saveNote}>
          Save
        </button>
      </div>
    </div>
  );
}

export default NoteEditor;

The NoteEditor the component is responsible for editing an existing note. It receives three props: note, updateNote, and cancelEdit.

Inside the component, we use the useState hook to define two state variables: editedTitle and editedContent. These variables hold the edited values of the note's title and content, respectively. We initialize them with the initial values from the note prop.

Next, we define two event handler functions: handleTitleChange and handleContentChange. These functions update the editedTitle and editedContent state variables, respectively, whenever the input fields for the title and content change.

The saveNote the function is called when the user clicks the "Save" button. It invokes the updateNote function (passed as a prop) with the updated note's ID, title, and content. This function will handle updating the note in the main app component.

πŸ“œ Step 13: NoteList Component

Next, we'll update the NoteList component to display the notes and enable editing and deletion. Open the NoteList.js file and update the code as follows:

import React from 'react';
import {FaEdit, FaTrashAlt} from "react-icons/fa";


function NoteList({ notes, deleteNote, setEditingNote }) {
  const handleEditNote = (note) => {
    setEditingNote(note);
  };

  const formatDate = (dateString) => {
    const date = new Date(dateString);
    return date.toLocaleString();
  };

  const getRandomColor = () => {
    const colors = ['#8db5e1', '#ffb6c1', '#f2e593', '#b7e2b2'];
    const randomIndex = Math.floor(Math.random() * colors.length);
    return colors[randomIndex];
  };

  return (
    <div className="note-list">
      {notes.map((note) => (
        <div className="note" key={note.id} style={{ backgroundColor: getRandomColor() }}>
          <h2>{note.title}</h2>
          <p>{note.content}</p>
          <p className="note-date">
            {note.updatedAt ? (
              `Last Updated: ${formatDate(note.updatedAt)}`
            ) : (
              `Created At: ${formatDate(note.createdAt)}`
            )}
          </p>
          <div className="note-buttons">
            <button className="edit-button" onClick={() => handleEditNote(note)}>
              Edit <FaEdit className='icon'/>
            </button>
            <button className="delete-button" onClick={() => deleteNote(note.id)}>
              Delete <FaTrashAlt className='icon'/>
            </button>
          </div>
        </div>
      ))}
    </div>
  );
}

export default NoteList;

In the NoteList component, we map over the notes array and render each note as an <li> element. We display the note's title and content and include buttons for editing and deleting the note. The deleteNote function is called the notes id when the delete button is clicked, and the setEditingNote function is called the note object when the edit button is clicked.

πŸ”₯ Step 13: Implementing Local Storage and Final App.js

To persist the notes even after refreshing the page, we'll use the browser's local storage. Update the App component code as follows:

import React, { useState, useEffect } from 'react';
import {FaRegStickyNote} from "react-icons/fa";
import { v4 as uuidv4 } from 'uuid';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';

import NoteForm from './components/NoteForm';
import NoteList from './components/NoteList';
import NoteEditor from './components/NoteEditor';

function App(){
  const [notes, setNotes] = useState([]);
  const [searchQuery, setSearchQuery] = useState('');
  const [editingNote, setEditingNote] = useState(null);

  const addNote = (title, content) => {
    const newNote = {
      id: uuidv4(), // Generate a unique ID for the note
      title,
      content,
      createdAt: new Date().toISOString(),
      updatedAt: null,
    };
    const updatedNotes = [...notes, newNote];
    setNotes(updatedNotes);
    localStorage.setItem('notes', JSON.stringify(updatedNotes));

    // Show a success toast notification after adding a note
    toast.success('Note added successfully!');
  };


  const deleteNote = (index) => {
    const updatedNotes = [...notes];
    updatedNotes.splice(index, 1);
    setNotes(updatedNotes);
    localStorage.setItem('notes', JSON.stringify(updatedNotes));

    // Show a Warn toast notification after deleting a note
    toast.error('Note deleted successfully!');
  };

  const updateNote = (id, title, content) => {
    const updatedNotes = notes.map((note) => {
      if (note.id === id) {
        return {
          ...note,
          title,
          content,
          updatedAt: new Date().toISOString(),
        };
      }
      return note;
    });
    setNotes(updatedNotes);
    setEditingNote(null);
    localStorage.setItem('notes', JSON.stringify(updatedNotes));

    // Show a success toast notification after updating a note
    toast.success('Note updated successfully!');
  };

  const cancelEdit = () => {
    setEditingNote(null);
  };

  useEffect(() => {
    const storedNotes = localStorage.getItem('notes');
    if (storedNotes) {
      setNotes(JSON.parse(storedNotes));
    }
  }, []);

  const filteredNotes = notes.filter(
    (note) =>
      note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
      note.content.toLowerCase().includes(searchQuery.toLowerCase())
  );

  return (
    <div className="app">
      <h1>Notes Keeping App <FaRegStickyNote className='icon'/></h1>
      {editingNote ? (
        <NoteEditor
          note={editingNote}
          updateNote={updateNote}
          cancelEdit={cancelEdit}
        />
      ) : (
        <NoteForm addNote={addNote} />
      )}
      <div className='search-input-container'><input
        type="text"
        placeholder="Search"
        value={searchQuery}
        onChange={(e) => setSearchQuery(e.target.value)}
        className="search-input"
      />
      </div>
      <NoteList notes={filteredNotes} deleteNote={deleteNote} setEditingNote={setEditingNote} />

      <ToastContainer />
    </div>
  );
};

export default App;

Here, we use the useEffect hook to load the stored notes from local storage when the component mounts. We parse the stored notes and update the state with the retrieved data. Additionally, we use another useEffect hook to save the notes in local storage whenever the `notes state changes.

πŸ”₯ Step 14: Final Touches and Deployment

At this point, the basic functionality of the Notes Keeping App is complete. You can further enhance the app by adding additional features like sorting notes, adding labels or categories, etc. Additionally, you can apply more styles and customize the UI to make it visually appealing.

Github Repo: Notes Keeping App

Once you're satisfied with the app, you can deploy it to a hosting platform of your choice. Some popular options include Netlify, Vercel, GitHub Pages, and Heroku. Here are the general steps to deploy a React app:

  1. Build the app: In the terminal, navigate to the project's root directory and run the following command:
npm run build

This will create an optimized production build of your app in the build directory.

  1. Choose a hosting platform: Select a hosting platform that best suits your needs and create an account if required.

  2. Deploy the app:

    • Netlify: Drag and drop the build folder to the Netlify dashboard or connect your repository to automate deployments.

    • Vercel: Use the Vercel CLI or connect your repository to Vercel to deploy the app.

    • GitHub Pages: Push the build folder to a GitHub repository's gh-pages branch and enable GitHub Pages in the repository settings.

Each hosting platform may have its own deployment process, so make sure to refer to their documentation for detailed instructions.

πŸŽ‰ Congratulations! Your Notes Keeping App is now ready to be used and shared with others. πŸš€πŸ“

Remember to regularly save your code and commit it to a version control system (such as Git) to track changes and collaborate with others effectively.

🌟 Step 15: Conclusion

In this development flow, we have successfully created a Notes Keeping App using React. We started by setting up the development environment, creating a new React project, and understanding the project structure. Then, we implemented the core functionality of the app, such as adding notes, deleting notes, and updating notes.

Throughout the process, we leveraged various React concepts and features, including state management with useState, side effects with useEffect, and conditional rendering. We also utilized external libraries like react-icons and uuid to enhance the app's functionality.

We discussed the importance of organizing components into reusable and modular units to maintain a clean and scalable codebase. We created three main components: NoteForm for adding new notes, NoteList for displaying the list of notes, and NoteEditor for editing existing notes.

Furthermore, we explored how to persist data using localStorage to store and retrieve notes, allowing users to access their notes even after refreshing the app.

To improve the user experience, we implemented toast notifications using the react-toastify library, providing feedback for successful note operations.

Finally, we discussed deployment options and provided a general guide for deploying a React app to popular hosting platforms like Netlify, Vercel, and GitHub Pages.

Enhancing the User Experience with Toast Notifications

To further improve the user experience, I added toast notifications using the react-toastify library. Toast notifications provide immediate feedback to the user, informing them about the outcome of their actions. Let's explore how toast notifications were integrated into the Notes Keeping App:

Adding a Note with Toast Notification βœοΈπŸ“Œ

When a user adds a new note, a toast notification is displayed to indicate the successful addition of the note. This immediate feedback assures the user that their action was successful. Here's the code snippet for adding the toast notification:

const addNote = (title, content) => {
  // ...

  // Show a success toast notification after adding a note
  toast.success('Note added successfully!');
};

By utilizing the toast.success function from the react-toastify library, a success toast notification is triggered, displaying a message that confirms the successful addition of the note.

Deleting a Note with Toast Notification πŸ—‘οΈβŒ

Similarly, when a user deletes a note, a toast notification is displayed to indicate the successful deletion of the note. This confirmation notification assures the user that the note has been successfully removed. Here's the code snippet for adding the toast notification:

const deleteNote = (index) => {
  // ...

  // Show a Warn toast notification after deleting a note
  toast.error('Note deleted successfully!');
};

Using the toast.error function, a warning toast notification is triggered, displaying a message that confirms the successful deletion of the note.

Updating a Note with Toast Notification πŸ“βœ¨

When a user updates a note, a toast notification is displayed to indicate the successful update of the note. This notification reassures the user that their changes have been saved. Here's the code snippet for adding the toast notification:

const updateNote = (id, title, content) => {
  // ...

  // Show a success toast notification after updating a note
  toast.success('Note updated successfully!');
};

By utilizing the toast.success function once again, a success toast notification is triggered, displaying a message that confirms the successful update of the note.

User Experience with Toast Notifications πŸš€πŸ’‘

By incorporating toast notifications into the Notes Keeping App, the overall user experience is significantly improved. Users receive immediate feedback when performing actions such as adding, deleting, or updating notes, ensuring that they are aware of the status of their actions. The toast notifications provide a visually appealing and informative way to communicate the outcome of these actions.

The react-toastify library offers various customization options, allowing you to tailor the appearance and behavior of the toast notifications to align with the overall design of your app. You can choose different toast types, durations, and positions, and even customize the appearance using CSS.

Conclusion and Next Steps 🎯

The addition of toast notifications has taken the Notes Keeping App to the next level in terms of user experience. Users can now receive instant feedback when performing actions, making their interaction with the app more seamless and satisfying.

As part of my 21-day frontend development challenge, I plan to continue building on this app by implementing additional features such as sorting notes, adding categories or tags, and incorporating user authentication to ensure data privacy. I also aim to optimize the app's performance and responsiveness for a better user experience across different devices.

Thank you for joining me on this exciting journey of front-end development! πŸš€πŸ’» Don't forget to follow me on LinkedIn for more updates, insights, and code breakdowns.

Let's keep coding and learning together! πŸ’»πŸš€

Happy coding! πŸ’»πŸš€

10
Subscribe to my newsletter

Read articles from Vinish Bhaskar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vinish Bhaskar
Vinish Bhaskar

Passionate Frontend Developer with a strong focus on ReactJS. Extensive experience in freelance web development, delivering 20+ SEO-optimized WordPress websites. Skilled in creating dynamic user interfaces using ReactJS, JavaScript, and CSS. Seeking immediate front-end opportunities to contribute expertise in ReactJS, JavaScript, CSS, and WordPress. Let's connect and discuss further details.