Project 3

Project 3
Notes App with Local Storage Persistence
Create, edit, and delete notes that survive a page refresh — no backend required
Difficulty: Intermediate Builds on: Fundamentals Ch 1–9

Project 1's todos vanished the moment the page reloaded — every piece of state was reset to nothing. This project adds persistence: saving the current data to the browser's localStorage on every change, and reading it back when the app first loads, so a note typed today is still there tomorrow. It's also the first project with a genuine "edit an existing item in place" flow, rather than just adding/removing whole items.

Skills This Project Exercises
localStorage useEffect (load + save) JSON.stringify / parse Editable list items Controlled textarea Component composition

Requirements

  • A way to create a new note with at least a title and a body of text.
  • All existing notes are listed, each showing its title and a preview (or full body) of its content.
  • Clicking a note lets you edit its title/body in place, saving the changes back to the same note.
  • Each note can be deleted individually.
  • The entire notes list is saved to localStorage whenever it changes — adding, editing, or deleting a note.
  • On page load/refresh, any previously saved notes are read back from localStorage and displayed — not lost.

Suggested Component Breakdown

App └── NotesApp // holds the notes array in state; loads/saves localStorage ├── NoteForm // title + body inputs, "Add Note" button └── NoteList // maps over notes, renders one NoteItem per note └── NoteItem (×N) // view mode + edit mode for one note, plus delete button

NoteItem is the trickiest piece — it needs its own small piece of local state (something like isEditing) to decide whether it's currently showing the note's static text or an editable form for it. That's a perfectly reasonable case for state living inside NoteItem itself rather than being lifted up, since no other component needs to know whether one particular note is currently being edited.

A Reasonable Build Order

  1. Build the add/list/delete flow first, exactly like Project 1's todo list, with notes only living in memory (no localStorage yet).
  2. Add a useEffect in NotesApp with the notes array as its dependency, calling localStorage.setItem("notes", JSON.stringify(notes)) every time it changes.
  3. Add a second useEffect (with an empty dependency array, running once on mount) that reads localStorage.getItem("notes"), parses it with JSON.parse, and sets it as the initial notes state — handling the case where nothing's been saved yet.
  4. Refresh the page after adding a few notes to confirm persistence is actually working before building anything else.
  5. Add edit mode to NoteItem last — its own isEditing state, a controlled form shown only while editing, and a "Save" action that updates the matching note in the parent's array.
localStorage only stores strings
localStorage.setItem can't store a JavaScript array or object directly — it needs to be converted to a string first with JSON.stringify, and converted back with JSON.parse when reading it. Forgetting either step is one of the most common bugs in a project like this — usually showing up as "[object Object]" being stored literally, or a crash when trying to .map() over a raw string.
Stretch Goals
  • Extract the load/save logic into a reusable useLocalStorage custom hook (a preview of Intermediate Chapter 4).
  • Add a search box that filters the visible notes by title/content as you type.
  • Store and display a "last edited" timestamp per note.
  • Add a confirmation step before deleting a note, to avoid accidental data loss.
  • Support basic Markdown-style formatting in the note body (e.g. rendering **bold** as bold text).
This project has no single solution file — the requirements above and your own judgment are the spec. Compare notes against the suggested component breakdown once you have something working, not before.