Project 3
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.
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
localStoragewhenever it changes — adding, editing, or deleting a note. - On page load/refresh, any previously saved notes are read back from
localStorageand displayed — not lost.
Suggested Component Breakdown
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
- Build the add/list/delete flow first, exactly like Project 1's todo list, with notes only living in memory (no
localStorageyet). - Add a
useEffectinNotesAppwith the notes array as its dependency, callinglocalStorage.setItem("notes", JSON.stringify(notes))every time it changes. - Add a second
useEffect(with an empty dependency array, running once on mount) that readslocalStorage.getItem("notes"), parses it withJSON.parse, and sets it as the initial notes state — handling the case where nothing's been saved yet. - Refresh the page after adding a few notes to confirm persistence is actually working before building anything else.
- Add edit mode to
NoteItemlast — its ownisEditingstate, a controlled form shown only while editing, and a "Save" action that updates the matching note in the parent's array.
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.
- Extract the load/save logic into a reusable
useLocalStoragecustom 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).