Step-by-Step Implementation of Our Notes App
Step-by-Step Implementation of Our Notes App
In this module, we’ll build the Sticky Notes App step by step. We’ll scaffold the React project using Vite, understand the directory structure, create the main component, add note-taking features (add, edit, delete, pin), implement search and drag-and-drop reordering, and finally add export/import functionality along with instructions and styling. By the end of this module, you’ll have a fully functional Notes App ready to use and deploy.
1) Set up the environment
We’ll use Vite for faster development setup.
Open your terminal and run:
npm create vite@latest notes-app
Choose the following options:
- Project name: notes-app
- Framework: React
- Variant: JavaScript
Now install dependencies and start the dev server:
cd notes-app
npm install
npm run dev
You’ll see the Vite + React starter page at a URL like:
http://localhost:5173/
2) Create Application Structure (Directory Structure)
After setup, your project will look like this:
notes-app/
├── index.html
├── package.json
├── vite.config.js
├── public/
└── src/
├── App.jsx
├── App.css
├── index.css
└── main.jsx
- index.html: Has <div id="root"></div>, where React renders our app.
- src/main.jsx: Entry file that mounts <App />.
- src/App.jsx: Main component where we’ll add our notes app logic.
- src/App.css & src/index.css: Styling files.
3) Rendering App in main.jsx
Open src/main.jsx. By default, it looks like this:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
This mounts our <App /> component into the root element in index.html.
4) Building Notes Functionality in App.jsx
Open src/App.jsx. Here we’ll add the full logic of our notes app.
Step 1: Import dependencies
import { useState, useEffect } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import "./App.css";
- useState: store notes, input text, search, and editing states.
- useEffect: save notes automatically to localStorage.
- react-beautiful-dnd: enable drag-and-drop ordering.
Step 2: Manage State
const [notes, setNotes] = useState(() => {
const saved = localStorage.getItem("notes");
return saved ? JSON.parse(saved) : [];
});
const [input, setInput] = useState("");
const [search, setSearch] = useState("");
const [editingId, setEditingId] = useState(null);
const [editText, setEditText] = useState("");
const [showInstructions, setShowInstructions] = useState(false);
- notes: all notes stored as objects.
- input: text for new note.
- search: search filter.
- editingId & editText: manage note editing.
- showInstructions: control modal visibility.
We also define random colors for note backgrounds:
const colors = ["#ffda77", "#77ffda", "#ff77e9", "#a7ff77", "#77a7ff", "#ffb577"];
Step 3: Add and Save Notes
const addNote = () => {
if (input.trim() === "") return;
const newNote = {
id: Date.now().toString(),
text: input,
pinned: false,
color: colors[Math.floor(Math.random() * colors.length)],
};
setNotes([newNote, ...notes]);
setInput("");
};
- Generates a unique id.
- Assigns random background color.
- Adds note at the top of the list.
Step 4: Edit, Pin, and Delete Notes
- Edit
const startEditing = (id, text) => {
setEditingId(id);
setEditText(text);
};
const saveEdit = (id) => {
setNotes(notes.map((n) => n.id === id ? { ...n, text: editText } : n));
setEditingId(null);
setEditText("");
};
- Double-click note to edit, press Enter or blur to save.
- Pin
const togglePin = (id) => {
setNotes(notes.map((n) => n.id === id ? { ...n, pinned: !n.pinned } : n));
};
Pinned notes always stay highlighted.
- Delete
const deleteNote = (id) => {
setNotes(notes.filter((n) => n.id !== id));
};
Step 5: Search and Sort Notes
const filteredNotes = notes
.filter((n) => n.text.toLowerCase().includes(search.toLowerCase()))
.sort((a, b) => (b.pinned === a.pinned ? 0 : b.pinned ? 1 : -1));
- Filters by text.
- Sorts pinned notes to the top.
Step 6: Drag-and-Drop Ordering
const handleDragEnd = (result) => {
if (!result.destination) return;
const items = Array.from(filteredNotes);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
setNotes(items);
};
Drag notes horizontally to reorder them.
Step 7: Export and Import Notes
- Export: Download all notes as JSON file.
- Import: Upload a JSON file to restore notes.
const exportNotes = () => {
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(notes, null, 2));
const downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", "notes.json");
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
};
const importNotes = (e) => {
const fileReader = new FileReader();
fileReader.onload = (event) => {
try {
const importedNotes = JSON.parse(event.target.result);
setNotes(importedNotes);
} catch {
alert("Invalid JSON file!");
}
};
fileReader.readAsText(e.target.files[0]);
};
Step 8: Instructions Modal
We add a modal to guide users with all app features (add, edit, pin, delete, drag, export/import). Controlled by showInstructions state.
Step 9: UI Return Statement
We build the interface with:
- Title + Instructions button
- Note input field and Add button
- Search bar
- Export/Import buttons
- Notes grid with drag-and-drop
- Footer with credit
- Instructions modal
5) Styling with CSS
All styles are inside App.css.
body {
background-color: #1e1e2f;
color: #f5f5f5;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 0;
}
.app {
text-align: center;
padding: 20px;
}
.title {
font-size: 2.5rem;
margin-bottom: 10px;
color: #ffda77;
}
.instructions-btn {
margin-bottom: 20px;
padding: 8px 15px;
border-radius: 8px;
border: none;
background: #77ddff;
color: #1e1e2f;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
}
.instructions-btn:hover {
background: #55ccee;
transform: scale(1.05);
}
.note-input {
margin-bottom: 20px;
}
.note-input textarea {
width: 300px;
height: 80px;
padding: 10px;
border-radius: 10px;
border: none;
outline: none;
font-size: 1rem;
background: #2a2a40;
color: #f5f5f5;
resize: none;
}
.note-input button {
margin-left: 10px;
padding: 10px 20px;
border-radius: 8px;
border: none;
cursor: pointer;
background: #ff77e9;
color: #fff;
font-weight: bold;
transition: 0.2s;
}
.note-input button:hover {
background: #ff44cc;
transform: scale(1.05);
}
.search-bar {
padding: 10px;
width: 250px;
border-radius: 8px;
border: none;
margin-bottom: 20px;
font-size: 1rem;
background: #2a2a40;
color: #f5f5f5;
outline: none;
}
.export-import {
margin: 20px 0;
display: flex;
justify-content: center;
gap: 15px;
}
.export-import button {
padding: 8px 15px;
border-radius: 8px;
border: none;
background: #77ddff;
color: #1e1e2f;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
}
.export-import button:hover {
background: #55ccee;
transform: scale(1.05);
}
.import-label {
padding: 8px 15px;
border-radius: 8px;
background: #ff77e9;
color: #fff;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
display: inline-block;
position: relative;
}
.import-label:hover {
background: #ff44cc;
transform: scale(1.05);
}
.import-label input {
display: none;
}
.notes-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
padding: 20px;
}
.note {
padding: 15px;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
position: relative;
transition: transform 0.2s, box-shadow 0.2s;
cursor: grab;
}
.note p {
margin: 0;
word-wrap: break-word;
white-space: pre-wrap;
}
.note-buttons {
position: absolute;
top: 8px;
right: 8px;
display: flex;
gap: 5px;
}
.note-buttons button {
border: none;
background: transparent;
font-size: 1.1rem;
cursor: pointer;
}
.edit-textarea {
width: 100%;
min-height: 60px;
padding: 8px;
border-radius: 8px;
border: 1px solid #ccc;
resize: none;
font-size: 1rem;
background: #fff;
color: #333;
}
footer {
margin-top: 30px;
font-size: 0.9rem;
color: #aaa;
}
footer a {
color: #77ddff;
text-decoration: none;
}
footer a:hover {
text-decoration: underline;
}
.fade-in {
animation: fadeIn 0.4s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9) rotate(-2deg);
}
to {
opacity: 1;
transform: scale(1) rotate(0deg);
}
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.75);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}
.modal-content {
background: #2a2a40;
color: #f5f5f5;
padding: 25px;
border-radius: 12px;
width: 90%;
max-width: 500px;
text-align: left;
position: relative;
}
.modal-content h2 {
text-align: center;
color: #ffda77;
}
.modal-content ul {
padding-left: 20px;
}
.modal-content button {
display: block;
margin: 20px auto 0 auto;
padding: 8px 15px;
border-radius: 8px;
border: none;
background: #77ddff;
color: #1e1e2f;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
}
.modal-content button:hover {
background: #55ccee;
transform: scale(1.05);
}
Key highlights:
- Dark background with light text for readability.
- Notes styled with random pastel colors, pinned notes in pink.
- Smooth hover effects on buttons.
- Modal backdrop for instructions with centered content.
- Drag-and-drop animations for notes (fade-in).
6) Final Touches and Deployment
- Test all features: add, edit, delete, pin, search, drag, export/import.
- Notes are automatically saved in localStorage.
- Deploy by running:
npm run build
This generates a dist/ folder. Upload it to Netlify or Vercel for instant hosting.
Now your Sticky Notes App is complete. It supports persistent storage, searching, editing, pinning, drag-and-drop, and easy export/import of notes—all wrapped in a clean, responsive UI.











