Next.js: Build a Simple Interactive Note-Taking App

Written by

in

In today’s fast-paced world, staying organized is key to productivity. Whether you’re a student, a professional, or just someone who likes to jot down ideas, a reliable note-taking app is invaluable. While there are many options available, building your own offers a fantastic learning experience, allowing you to understand the underlying principles and customize it to your specific needs. In this tutorial, we’ll walk through building a simple, yet functional, interactive note-taking app using Next.js, a powerful React framework.

Why Build a Note-Taking App?

Creating a note-taking app provides several benefits. Firstly, it solidifies your understanding of core web development concepts like state management, event handling, and data persistence. Secondly, it gives you a practical project to showcase your skills. Finally, it allows you to build something tailored to your workflow, unlike pre-built apps that might not fit your exact requirements. This project is ideal for those looking to expand their Next.js knowledge and create something useful in the process.

What We’ll Build

Our note-taking app will allow users to:

  • Create new notes
  • Edit existing notes
  • Delete notes
  • View a list of all notes

We’ll keep it simple, focusing on the essential functionality to make it a manageable project for beginners. We’ll utilize Next.js for its server-side rendering and routing capabilities, making the app performant and SEO-friendly. We’ll also use local storage to persist the notes, so they remain even after the browser is closed. This tutorial will guide you step-by-step, explaining each part of the code and the reasoning behind it.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js and npm (or yarn) installed on your system.
  • A basic understanding of HTML, CSS, and JavaScript.
  • Familiarity with React components and JSX.
  • A code editor (like VS Code) installed.

Setting Up the Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app note-taking-app

This command sets up a basic Next.js project with all the necessary dependencies. Navigate into your project directory:

cd note-taking-app

Now, let’s install a library for handling unique IDs. While not strictly necessary, it’s good practice to assign unique IDs to each note. We’ll use the uuid library:

npm install uuid

Creating the Note Component

We’ll start by creating a component to represent a single note. Create a new file named Note.js inside the components directory (you may need to create this directory if it doesn’t exist):

// components/Note.js
import React from 'react';

function Note({ note, onDelete, onEdit }) {
  return (
    <div className="note">
      <h3>{note.title}</h3>
      <p>{note.content}</p>
      <div className="note-actions">
        <button onClick={() => onEdit(note.id)}>Edit</button>
        <button onClick={() => onDelete(note.id)}>Delete</button>
      </div>
    </div>
  );
}

export default Note;

In this component:

  • We receive a note object as a prop, containing the title and content of the note.
  • We display the title in an <h3> tag and the content in a <p> tag.
  • We have two buttons: “Edit” and “Delete”, which call the onEdit and onDelete functions, respectively, passing the note’s ID.

Creating the Notes List Component

Next, we’ll create a component to display the list of notes. Create a file named NotesList.js inside the components directory:

// components/NotesList.js
import React from 'react';
import Note from './Note';

function NotesList({ notes, onDelete, onEdit }) {
  return (
    <div className="notes-list">
      {notes.map((note) => (
        <Note
          key={note.id}
          note={note}
          onDelete={onDelete}
          onEdit={onEdit}
        />
      ))}
    </div>
  );
}

export default NotesList;

This component:

  • Receives an array of notes as a prop.
  • Maps over the notes array and renders a Note component for each note.
  • Passes the note object, onDelete, and onEdit functions to each Note component.
  • Uses the unique id of each note as the key prop for the Note component, which is crucial for React’s efficient rendering.

Creating the Form Component

Now, let’s create a form component for creating and editing notes. Create a file named NoteForm.js inside the components directory:

// components/NoteForm.js
import React, { useState, useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';

function NoteForm({ onAddNote, onUpdateNote, noteToEdit, onCancelEdit }) {
  const [title, setTitle] = useState('');
  const [content, setContent] = useState('');

  useEffect(() => {
    if (noteToEdit) {
      setTitle(noteToEdit.title);
      setContent(noteToEdit.content);
    }
  }, [noteToEdit]);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (title.trim() === '' || content.trim() === '') {
      alert('Please fill in both title and content.');
      return;
    }

    if (noteToEdit) {
      // Update existing note
      onUpdateNote(noteToEdit.id, { title, content });
    } else {
      // Add new note
      const newNote = {
        id: uuidv4(),
        title,
        content,
      };
      onAddNote(newNote);
    }

    setTitle('');
    setContent('');
    if (noteToEdit) {
        onCancelEdit(); // Clear edit mode after submit
    }
  };

  return (
    <form onSubmit={handleSubmit} className="note-form">
      <label htmlFor="title">Title:</label>
      <input
        type="text"
        id="title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <label htmlFor="content">Content:</label>
      <textarea
        id="content"
        value={content}
        onChange={(e) => setContent(e.target.value)}
      />
      <div className="form-actions">
        <button type="submit">{noteToEdit ? 'Update Note' : 'Add Note'}</button>
        {noteToEdit && (
          <button type="button" onClick={onCancelEdit}>Cancel</button>
        )}
      </div>
    </form>
  );
}

export default NoteForm;

This component:

  • Uses the useState hook to manage the title and content input fields.
  • Uses the useEffect hook to populate the form fields when editing a note.
  • Receives onAddNote, onUpdateNote, noteToEdit, and onCancelEdit as props.
  • Handles form submission by either adding a new note or updating an existing one, based on the presence of noteToEdit.
  • Uses the uuidv4() function from the uuid library to generate a unique ID for new notes.
  • Includes a cancel button when editing a note.

Building the Main App Component

Now, let’s create the main app component that ties everything together. Modify the pages/index.js file:

// pages/index.js
import React, { useState, useEffect } from 'react';
import { v4 as uuidv4 } from 'uuid';
import NotesList from '../components/NotesList';
import NoteForm from '../components/NoteForm';

function Home() {
  const [notes, setNotes] = useState([]);
  const [noteToEdit, setNoteToEdit] = useState(null);

  useEffect(() => {
    // Load notes from local storage on component mount
    if (typeof window !== 'undefined') {
      const storedNotes = localStorage.getItem('notes');
      if (storedNotes) {
        setNotes(JSON.parse(storedNotes));
      }
    }
  }, []);

  useEffect(() => {
    // Save notes to local storage whenever notes change
    if (typeof window !== 'undefined') {
      localStorage.setItem('notes', JSON.stringify(notes));
    }
  }, [notes]);

  const handleAddNote = (newNote) => {
    setNotes([...notes, newNote]);
  };

  const handleUpdateNote = (id, updatedNote) => {
    setNotes(
      notes.map((note) => (note.id === id ? { ...note, ...updatedNote } : note))
    );
    setNoteToEdit(null); // Clear edit mode after update
  };

  const handleDeleteNote = (id) => {
    setNotes(notes.filter((note) => note.id !== id));
  };

  const handleEditNote = (id) => {
    const noteToEdit = notes.find((note) => note.id === id);
    setNoteToEdit(noteToEdit);
  };

  const handleCancelEdit = () => {
    setNoteToEdit(null);
  };

  return (
    <div className="container">
      <h1>My Notes</h1>
      <NoteForm
        onAddNote={handleAddNote}
        onUpdateNote={handleUpdateNote}
        noteToEdit={noteToEdit}
        onCancelEdit={handleCancelEdit}
      />
      <NotesList notes={notes} onDelete={handleDeleteNote} onEdit={handleEditNote} />
    </div>
  );
}

export default Home;

In this component:

  • We use the useState hook to manage the notes state and the noteToEdit state.
  • We use useEffect hooks to load notes from local storage on component mount and save notes to local storage whenever the notes state changes. This ensures that the notes persist across browser sessions. The typeof window !== 'undefined' check is crucial for server-side rendering in Next.js, preventing errors during the initial render.
  • We define functions to handle adding, updating, deleting, and editing notes.
  • We render the NoteForm and NotesList components, passing the necessary props.

Styling the App (Optional but Recommended)

While the app will function without styling, adding CSS will significantly improve the user experience. You can add styles to a globals.css file (located in the styles directory). Here’s a basic example:

/* styles/globals.css */
body {
  font-family: sans-serif;
  margin: 0;
  padding: 20px;
  background-color: #f4f4f4;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  background-color: #fff;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1 {
  text-align: center;
  color: #333;
}

.note {
  background-color: #eee;
  padding: 15px;
  margin-bottom: 15px;
  border-radius: 4px;
}

.note h3 {
  margin-top: 0;
  color: #555;
}

.note-actions {
  margin-top: 10px;
}

.note-actions button {
  margin-right: 10px;
  padding: 8px 12px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.note-actions button:hover {
  opacity: 0.8;
}

.note-form {
  margin-bottom: 20px;
}

.note-form label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

.note-form input[type="text"], textarea {
  width: 100%;
  padding: 10px;
  margin-bottom: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
  box-sizing: border-box;
}

.form-actions button {
  padding: 10px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  background-color: #007bff;
  color: white;
}

.form-actions button:hover {
  opacity: 0.8;
}

Feel free to customize the styles to match your preferences.

Running the App

To run the app, execute the following command in your terminal:

npm run dev

This will start the development server. Open your browser and navigate to http://localhost:3000 to see your note-taking app in action.

Common Mistakes and Troubleshooting

Here are some common mistakes and how to fix them:

  • Incorrect imports: Double-check that you’ve imported the components and libraries correctly. Typos in import statements are a frequent source of errors.
  • State not updating: Ensure you are correctly updating the state using the setNotes function. Remember that state updates in React are asynchronous.
  • Local storage issues: Make sure you’re using typeof window !== 'undefined' when accessing local storage to avoid errors during server-side rendering. Also, verify that the data is being saved and retrieved correctly in local storage using your browser’s developer tools.
  • Missing keys in mapped elements: When rendering lists with .map(), always provide a unique key prop to each element. This helps React efficiently update the DOM.
  • CSS not applying: Ensure that your CSS files are correctly linked and that there are no CSS conflicts. Check for typos in your CSS selectors.

Key Takeaways

  • You’ve learned how to build a basic note-taking app using Next.js.
  • You’ve gained experience with state management, event handling, and local storage.
  • You’ve learned how to create reusable components.
  • You have a working example to expand upon and customize to your needs.

FAQ

  1. Can I deploy this app? Yes, you can deploy this app to platforms like Vercel or Netlify, which are specifically designed for Next.js applications.
  2. How can I add more features? You can add features like rich text editing, tagging, search functionality, and more. Consider using libraries like Draft.js or React Quill for rich text editing.
  3. How do I handle errors? Implement error handling using try...catch blocks to gracefully handle potential issues, such as errors when interacting with local storage or external APIs.
  4. How can I improve the UI/UX? Experiment with different UI components, styling libraries (like Tailwind CSS or Material UI), and user interaction patterns to create a more engaging user experience.

This tutorial provides a solid foundation for building a note-taking app. By understanding the core concepts and following the steps outlined, you can create a functional and useful application. Remember, the best way to learn is by doing. Experiment with the code, add new features, and customize it to your liking. Happy coding!

With this foundation, you can now explore more advanced features like incorporating a database for persistent storage, implementing user authentication, or even integrating a rich text editor for more complex note formatting. The possibilities are endless, and this project serves as a perfect starting point for your journey into building practical and engaging web applications with Next.js.