Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to build a notes app in React

A notes application is an ideal project for mastering React state management, CRUD operations, and data persistence. As the creator of CoreUI with over 10 years of React experience since 2014, I’ve built note-taking interfaces for knowledge management tools, CMS editors, and productivity dashboards. The most effective approach uses useState for notes, useEffect for localStorage persistence, and controlled inputs for editing. This delivers a fully functional notes app with minimal complexity.

Create the core notes state and display structure.

import { useState, useEffect } from 'react'

function NotesApp() {
  const [notes, setNotes] = useState(() => {
    const saved = localStorage.getItem('notes')
    return saved ? JSON.parse(saved) : []
  })
  const [activeId, setActiveId] = useState(null)

  useEffect(() => {
    localStorage.setItem('notes', JSON.stringify(notes))
  }, [notes])

  const createNote = () => {
    const note = { id: Date.now(), title: 'New Note', body: '', updatedAt: Date.now() }
    setNotes([note, ...notes])
    setActiveId(note.id)
  }

  const deleteNote = (id) => {
    setNotes(notes.filter(n => n.id !== id))
    if (activeId === id) setActiveId(null)
  }

  const activeNote = notes.find(n => n.id === activeId)

  return (
    <div className="notes-app">
      <aside className="sidebar">
        <button onClick={createNote}>+ New Note</button>
        <ul>
          {notes.map(note => (
            <li
              key={note.id}
              className={note.id === activeId ? 'active' : ''}
              onClick={() => setActiveId(note.id)}
            >
              <strong>{note.title || 'Untitled'}</strong>
              <button onClick={(e) => { e.stopPropagation(); deleteNote(note.id) }}>×</button>
            </li>
          ))}
        </ul>
      </aside>
      <main>
        {activeNote ? (
          <NoteEditor note={activeNote} setNotes={setNotes} notes={notes} />
        ) : (
          <p>Select a note or create a new one</p>
        )}
      </main>
    </div>
  )
}

Notes load from localStorage on mount via the useState initializer. The activeId tracks which note is open. New notes are prepended so the latest appears first. Deleting the active note clears the editor.

Building the Note Editor

Create an editor that updates notes as you type.

function NoteEditor({ note, notes, setNotes }) {
  const updateNote = (field, value) => {
    setNotes(notes.map(n =>
      n.id === note.id
        ? { ...n, [field]: value, updatedAt: Date.now() }
        : n
    ))
  }

  const formatDate = (timestamp) =>
    new Date(timestamp).toLocaleString('en-US', {
      month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit'
    })

  return (
    <div className="note-editor">
      <input
        className="note-title"
        type="text"
        value={note.title}
        onChange={(e) => updateNote('title', e.target.value)}
        placeholder="Note title"
      />
      <p className="note-meta">Last updated: {formatDate(note.updatedAt)}</p>
      <textarea
        className="note-body"
        value={note.body}
        onChange={(e) => updateNote('body', e.target.value)}
        placeholder="Start writing..."
      />
    </div>
  )
}

updateNote patches the changed field while preserving other fields. It also updates the timestamp. Changes persist to localStorage via the parent’s useEffect. The editor is fully controlled.

Adding Search Functionality

Filter notes by title and content.

function NotesApp() {
  const [notes, setNotes] = useState(() => {
    const saved = localStorage.getItem('notes')
    return saved ? JSON.parse(saved) : []
  })
  const [activeId, setActiveId] = useState(null)
  const [search, setSearch] = useState('')

  useEffect(() => {
    localStorage.setItem('notes', JSON.stringify(notes))
  }, [notes])

  const filteredNotes = notes.filter(note =>
    note.title.toLowerCase().includes(search.toLowerCase()) ||
    note.body.toLowerCase().includes(search.toLowerCase())
  )

  return (
    <div className="notes-app">
      <aside className="sidebar">
        <input
          type="text"
          value={search}
          onChange={(e) => setSearch(e.target.value)}
          placeholder="Search notes..."
        />
        <ul>
          {filteredNotes.map(note => (
            <li key={note.id} onClick={() => setActiveId(note.id)}>
              {note.title || 'Untitled'}
            </li>
          ))}
        </ul>
      </aside>
    </div>
  )
}

filteredNotes filters in real time as search changes. Both title and body are searched. The original notes array stays unchanged so clearing the search restores the full list.

Sorting Notes

Sort by last updated or alphabetically.

const [sortBy, setSortBy] = useState('updated')

const sortedNotes = [...filteredNotes].sort((a, b) => {
  if (sortBy === 'updated') return b.updatedAt - a.updatedAt
  return a.title.localeCompare(b.title)
})

Sorting creates a new array to avoid mutating state. Date sort puts recently edited notes first. Title sort uses localeCompare for correct alphabetical order. Add a dropdown to let users switch between sort modes.

Best Practice Note

This is the same notes architecture we use in CoreUI content management templates. For production apps, replace localStorage with an API backend so notes sync across devices. Add Markdown rendering with a library like react-markdown for rich formatting. Implement auto-save with a debounced useEffect to avoid excessive writes on every keystroke. Add keyboard shortcut Ctrl+N for creating new notes quickly. Consider tags or folders for organization as the notes collection grows.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to remove a property from an object in Javascript
How to remove a property from an object in Javascript

How to Get Unique Values from a JavaScript Array
How to Get Unique Values from a JavaScript Array

How to Disable Right Click on a Website Using JavaScript
How to Disable Right Click on a Website Using JavaScript

How to get element ID in JavaScript
How to get element ID in JavaScript

Answers by CoreUI Core Team