Sticky Notes Board React Project Tutorial with Code

Step-by-Step Implementation of Sticky Board

The goal of this module is to set up your environment, scaffold the app, and write the code step by step until we have a working sticky notes board running locally.

Set Up the Environment

Before coding, make sure your machine is ready:

  1. Install Node.js (LTS)
    • Download from nodejs.org.
    • Verify installation:
      node -v
      npm -v
  2. Install a Code Editor
    • Use VS Code.

Once that’s done, open your terminal and create the project with Create React App:

npx create-react-app sticky-board

cd sticky-board

npm start

Your browser should open at http://localhost:3000 with the React default page. That means the setup worked.

Create Application Structure (Directory Layout)

Here’s the structure we’ll end up with inside src/:

src/

├─ components/

│  ├─ Board.js

│  ├─ Note.js

├─ utils.js

├─ App.js

├─ index.js

├─ index.css

  • components/ → Holds all reusable UI pieces (Board and Note).
  • utils.js → Helper functions for localStorage and ID generation.
  • App.js → Main logic + form handling.
  • index.css → Styling.
  • index.js → Entry point.

Steps Needed to Build the Project

Let’s break down what we’ll actually do:

1.  Create Utility Functions (src/utils.js)

In the src folder, create a file called utils.js and fill in the below code:

const STORAGE_KEY = 'sticky_notes_board_v1';
// Save notes to localStorage
export function saveNotes(notes) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
}
// Load notes from localStorage
export function loadNotes() {
  const data = localStorage.getItem(STORAGE_KEY);
  return data ? JSON.parse(data) : [];
}
// Generate a unique ID for each note
export function uid() {
  return Math.random().toString(36).slice(2, 9);
}

Explanation:

  • saveNotes → stores notes as a JSON string in localStorage.
  • loadNotes → retrieves notes or returns an empty array.
  • uid → generates a random string to use as a unique ID.

2. Note Component (src/components/Note.js)

In the src folder, create another folder called components, and there, create a file called Note.js and then paste the below code:

import React, { useState } from 'react';

export default function Note({ note, onDelete, onUpdate }) {
  const [editing, setEditing] = useState(false);
  const [title, setTitle] = useState(note.title);
  const [body, setBody] = useState(note.body);
  function saveChanges() {
    onUpdate({ ...note, title: title.trim(), body: body.trim(), updatedAt: Date.now() });
    setEditing(false);
  }
  return (
    <div className="note">
      <div className="actions">
        <button onClick={() => setEditing(!editing)}>
          {editing ? 'Cancel' : 'Edit'}
        </button>
        <button onClick={() => onDelete(note.id)}>Delete</button>
      </div>
      {editing ? (
        <>
          <input
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            placeholder="Title"
          />
          <textarea
            value={body}
            onChange={(e) => setBody(e.target.value)}
            placeholder="Write your note..."
          />
          <button onClick={saveChanges}>Save</button>
        </>
      ) : (
        <>
          <h3>{note.title || 'Untitled'}</h3>
          <small>{new Date(note.updatedAt || note.createdAt).toLocaleString()}</small>
          <p>{note.body}</p>
        </>
      )}
    </div>
  );
}

Explanation:

  • Supports toggling between view and edit mode.
  • onUpdate updates the note with new content.
  • onDelete removes the note.

3. Board Component (src/components/Board.js)

Similarly, in the same folder, create another file called Board.js and fill in the code below:

import React from 'react';
import Note from './Note';
export default function Board({ notes, onDelete, onUpdate }) {
  if (!notes.length) {
    return <div className="empty">No notes yet. Add one above!</div>;
  }
  return (
    <div className="board">
      {notes.map((n) => (
        <Note key={n.id} note={n} onDelete={onDelete} onUpdate={onUpdate} />
      ))}
    </div>
  );
}

Explanation:

  • Receives notes from App as props.
  • Maps over notes → renders a Note component for each one.
  • Shows a fallback message if no notes exist.

4. App Component (src/App.js)

Edit the App.js file in the src folder by pasting the below code:

import React, { useEffect, useState } from 'react';
import Board from './components/Board';
import { saveNotes, loadNotes, uid } from './utils';
function App() {
  const [notes, setNotes] = useState([]);
  const [title, setTitle] = useState('');
  const [body, setBody] = useState('');
  // Load notes from localStorage on first render
  useEffect(() => {
    setNotes(loadNotes());
  }, []);
  // Save notes to localStorage whenever they change
  useEffect(() => {
    saveNotes(notes);
  }, [notes]);
  function addNote(e) {
    e.preventDefault();
    if (!title.trim() && !body.trim()) return;

    const now = Date.now();
    const newNote = {
      id: uid(),
      title: title.trim(),
      body: body.trim(),
      createdAt: now,
      updatedAt: now,
    };

    setNotes([newNote, ...notes]);
    setTitle('');
    setBody('');
  }

  function deleteNote(id) {
    setNotes(notes.filter((n) => n.id !== id));
  }

  function updateNote(updated) {
    setNotes(notes.map((n) => (n.id === updated.id ? updated : n)));
  }

  return (
    <div className="app">
      <h1>Sticky Notes Board</h1>

      <form className="form" onSubmit={addNote}>
        <input
          placeholder="Title"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
        />
        <textarea
          placeholder="Write your note..."
          value={body}
          onChange={(e) => setBody(e.target.value)}
        />
        <button type="submit">Add Note</button>
      </form>

      <Board notes={notes} onDelete={deleteNote} onUpdate={updateNote} />
    </div>
  );
}

export default App;

Explanation:

  • Manages state for notes, title, and body.
  • useEffect loads notes on mount and saves them when updated.
  • Provides addNote, deleteNote, and updateNote handlers.
  • Renders the form and passes data/functions to Board.

5. Styling (src/index.css)

It is time to style up your sticky pad. In the index.css file, paste the below code:

/* Global styles */
body {
  font-family: 'Segoe UI', Roboto, Arial, sans-serif;
  background: #f0ece2 url("https://www.transparenttextures.com/patterns/gray-floral.png");
  margin: 0;
  padding: 0;
  color: #333;
}

.app {
  max-width: 1000px;
  margin: 40px auto;
  padding: 20px;
}

h1 {
  text-align: center;
  font-size: 2rem;
  margin-bottom: 25px;
  color: #222;
  text-shadow: 1px 1px 0px rgba(0,0,0,0.05);
}

/* Form styling */
.form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 25px;
  background: #fff;
  padding: 15px;
  border-radius: 10px;
  box-shadow: 0px 4px 10px rgba(0,0,0,0.1);
}

.form input,
.form textarea {
  padding: 12px;
  border: 1px solid #ccc;
  border-radius: 6px;
  font-size: 14px;
  resize: none;
  transition: border 0.2s ease;
}

.form input:focus,
.form textarea:focus {
  outline: none;
  border: 1px solid #1f7aef;
  box-shadow: 0 0 4px rgba(31,122,239,0.3);
}

.form button {
  align-self: flex-start;
  padding: 10px 18px;
  border: none;
  background: #1f7aef;
  color: white;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 500;
  transition: background 0.2s ease;
}

.form button:hover {
  background: #1663c7;
}

/* Board layout */
.board {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  justify-content: center;
}

/* Sticky note styles */
.note {
  width: 220px;
  min-height: 140px;
  padding: 15px;
  border-radius: 8px;
  box-shadow: 0px 6px 12px rgba(0,0,0,0.15);
  position: relative;
  transform: rotate(-1deg);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
  cursor: grab;
  overflow-wrap: break-word;
}

/* Random sticky note background colors */
.note:nth-child(3n+1) {
  background: #fff176; /* yellow */
}
.note:nth-child(3n+2) {
  background: #ffab91; /* peach */
}
.note:nth-child(3n+3) {
  background: #80deea; /* teal */
}

.note:hover {
  transform: scale(1.05) rotate(0deg);
  box-shadow: 0px 10px 18px rgba(0,0,0,0.25);
}

/* Note text */
.note h3 {
  margin-top: 0;
  font-size: 16px;
}

.note p {
  font-size: 14px;
  margin: 8px 0;
  white-space: pre-wrap;
}

.note small {
  font-size: 11px;
  color: #444;
}

/* Action buttons */
.note .actions {
  position: absolute;
  top: 8px;
  right: 8px;
  display: flex;
  gap: 6px;
}

.note button {
  padding: 4px 8px;
  border: none;
  border-radius: 4px;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.2s ease;
}

.note button:hover {
  opacity: 0.9;
}

.note button:nth-child(1) {
  background: #ffd54f; /* edit */
}
.note button:nth-child(2) {
  background: #ef5350; /* delete */
  color: white;
}

/* Empty state */
.empty {
  padding: 30px;
  text-align: center;
  color: #555;
  font-style: italic;
  border: 2px dashed #ccc;
  border-radius: 10px;
  background: #fff;
}

Explanation:

  • Makes notes look like sticky cards with a yellow background.
  • Styles the form and board layout.
  • Adds hover effects to buttons for better UX.

At this point, your Sticky Notes Board is complete and runs locally.

Run:

npm start

Go to http://localhost:3000 → Add, edit, delete notes, and refresh the page. Your notes should persist.