In today’s fast-paced digital world, keeping track of ideas, tasks, and important information is crucial. While there are countless note-taking apps available, building your own offers a unique opportunity to learn fundamental React.js concepts and customize a tool that perfectly fits your needs. This tutorial will guide you through creating a simple, yet functional, React JavaScript Notes App from scratch. We’ll cover everything from setting up your development environment to implementing features like adding, editing, and deleting notes. By the end, you’ll not only have a practical application but also a solid understanding of React’s core principles.
Why Build a Notes App?
Creating a notes app is an excellent project for beginner to intermediate React developers for several reasons:
- Practical Application: It’s a useful tool that you can actually use daily, making the learning process more engaging.
- Core Concepts: It allows you to practice essential React concepts like components, state management, event handling, and conditional rendering.
- Scalability: The basic structure can be easily extended with more advanced features like search, tagging, and cloud storage integration.
- Portfolio Piece: It’s a great project to showcase your React skills to potential employers or clients.
This tutorial will focus on building a simple, single-page notes app. We will use functional components and hooks, which are the modern way to write React code. We will not use any external libraries for this project, except for a CSS reset. This means you will learn how to handle state, events, and rendering directly within React, giving you a deeper understanding of the framework.
Prerequisites
Before we begin, make sure you have the following:
- Node.js and npm (or yarn) installed: You’ll need these to manage project dependencies. You can download Node.js from nodejs.org.
- A text editor or IDE: VS Code, Sublime Text, or Atom are popular choices.
- Basic understanding of HTML, CSS, and JavaScript: Familiarity with these languages is essential for understanding the code.
Setting Up the Project
Let’s get started by creating a new React project. Open your terminal and run the following command:
npx create-react-app react-notes-app
cd react-notes-app
This command creates a new React project named “react-notes-app” and navigates you into the project directory. Next, we will clean up the default files and install a CSS reset. Navigate to the src directory and delete the following files: App.css, App.test.js, logo.svg, reportWebVitals.js, setupTests.js and replace the content of App.js and index.js with the code below.
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
App.js
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<h1>React Notes App</h1>
</header>
<main>
<p>Your notes will appear here.</p>
</main>
</div>
);
}
export default App;
index.css
/* index.css */
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f4f4f4;
color: #333;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
App.css
/* App.css */
.App {
text-align: center;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
padding: 20px;
}
main {
padding: 20px;
}
Now, run the following command in your terminal to start the development server:
npm start
This will open your app in your browser at `http://localhost:3000`. You should see the basic “React Notes App” header and a placeholder text.
Creating the Note Component
Let’s create a `Note` component to represent each individual note. Create a new file named `Note.js` inside the `src` directory.
Note.js
import React from 'react';
function Note({ note, onDelete, onEdit }) {
return (
<div className="note">
<p>{note.text}</p>
<div className="note-actions">
<button onClick={() => onEdit(note.id)}>Edit</button>
<button onClick={() => onDelete(note.id)}>Delete</button>
</div>
</div>
);
}
export default Note;
In this component:
- We receive a `note` prop, which is an object containing the note’s text and, later, its ID.
- We display the `note.text` within a <p> tag.
- We include “Edit” and “Delete” buttons. These buttons will trigger `onEdit` and `onDelete` functions (passed as props from the parent component) when clicked. We’ll implement these functions later.
- We use a basic CSS class “note” for styling.
Now, let’s add some basic CSS for the note component. Add the following to your `App.css` file:
.note {
background-color: #fff;
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
text-align: left;
}
.note-actions {
margin-top: 10px;
}
.note-actions button {
margin-right: 5px;
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.note-actions button:hover {
opacity: 0.8;
}
Implementing the App Component
Now, let’s modify the `App.js` file to include the `Note` component, manage the notes’ state, and add functionality for adding, editing, and deleting notes. Replace the content of `App.js` with the following code:
import React, { useState } from 'react';
import './App.css';
import Note from './Note';
function App() {
const [notes, setNotes] = useState([]);
const [newNoteText, setNewNoteText] = useState('');
const [editingNoteId, setEditingNoteId] = useState(null);
const [editText, setEditText] = useState('');
const handleAddNote = () => {
if (newNoteText.trim() !== '') {
const newNote = {
id: Date.now(), // Simple unique ID
text: newNoteText,
};
setNotes([...notes, newNote]);
setNewNoteText('');
}
};
const handleDeleteNote = (id) => {
setNotes(notes.filter((note) => note.id !== id));
};
const handleEditNote = (id) => {
const noteToEdit = notes.find(note => note.id === id);
if (noteToEdit) {
setEditingNoteId(id);
setEditText(noteToEdit.text);
}
};
const handleUpdateNote = () => {
if (editText.trim() !== '' && editingNoteId !== null) {
setNotes(
notes.map(note =>
note.id === editingNoteId ? { ...note, text: editText } : note
)
);
setEditingNoteId(null);
setEditText('');
}
};
const handleCancelEdit = () => {
setEditingNoteId(null);
setEditText('');
}
return (
<div className="App">
<header className="App-header">
<h1>React Notes App</h1>
</header>
<main>
<div className="note-input">
<input
type="text"
placeholder="Add a new note"
value={newNoteText}
onChange={(e) => setNewNoteText(e.target.value)}
/>
<button onClick={handleAddNote}>Add Note</button>
</div>
{editingNoteId !== null ? (
<div className="edit-note">
<input
type="text"
value={editText}
onChange={(e) => setEditText(e.target.value)}
/>
<button onClick={handleUpdateNote}>Update</button>
<button onClick={handleCancelEdit}>Cancel</button>
</div>
) : null}
<div className="notes-container">
{notes.map((note) => (
<Note
key={note.id}
note={note}
onDelete={handleDeleteNote}
onEdit={handleEditNote}
/>
))}
</div>
</main>
</div>
);
}
export default App;
Let’s break down the code:
- State Variables:
- `notes`: An array to store the notes. Initialized as an empty array using `useState([])`.
- `newNoteText`: A string to store the text entered in the “Add Note” input field. Initialized as an empty string.
- `editingNoteId`: Stores the id of the note being edited. Initialized to `null`.
- `editText`: Stores the text of the note being edited. Initialized to `”`.
- `handleAddNote()` Function:
- This function is triggered when the “Add Note” button is clicked.
- It checks if `newNoteText` is not empty.
- If not empty, it creates a new note object with a unique `id` (using `Date.now()`, which is good enough for a simple app) and the `newNoteText`.
- It updates the `notes` state by adding the new note to the existing notes array using the spread operator (`…`).
- Finally, it clears the `newNoteText` input field.
- `handleDeleteNote(id)` Function:
- This function is called when the “Delete” button of a `Note` is clicked.
- It receives the `id` of the note to be deleted.
- It updates the `notes` state by filtering out the note with the matching `id`.
- `handleEditNote(id)` Function:
- This function is called when the “Edit” button of a `Note` is clicked.
- It receives the `id` of the note to be edited.
- It sets the `editingNoteId` to the `id`, indicating that we are editing a note.
- It sets the `editText` to the current text of the note being edited.
- `handleUpdateNote()` Function:
- This function is called when the “Update” button is clicked.
- It checks if the `editText` is not empty and that `editingNoteId` is not null.
- If the conditions are met, it updates the `notes` state by mapping through the existing notes and updating the text of the note with the matching `id`.
- It resets the `editingNoteId` and `editText`.
- `handleCancelEdit()` Function:
- This function is called when the “Cancel” button is clicked during editing.
- It resets the `editingNoteId` and `editText`.
- JSX Structure:
- An input field and an “Add Note” button are displayed. The input field’s value is bound to `newNoteText`, and its `onChange` event updates `newNoteText`.
- Conditionally renders an edit input and update/cancel buttons if `editingNoteId` is not null.
- The `notes` array is mapped to render a `Note` component for each note.
- The `Note` component receives the `note` data, `onDelete`, and `onEdit` functions as props.
Let’s add some CSS for the input and button. Add the following to your `App.css` file:
.note-input {
margin-bottom: 20px;
}
.note-input input[type="text"] {
padding: 8px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}
.note-input button {
padding: 8px 15px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.note-input button:hover {
opacity: 0.8;
}
.notes-container {
width: 80%;
max-width: 600px;
}
.edit-note {
margin-bottom: 10px;
}
.edit-note input[type="text"] {
padding: 8px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}
.edit-note button {
padding: 8px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 5px;
}
.edit-note button:hover {
opacity: 0.8;
}
Now, run your app (`npm start`) and test the functionality: adding, editing, and deleting notes. You should be able to create notes, edit them, and delete them. If you encounter problems, double-check your code against the provided snippets and inspect the browser’s console for error messages. Common issues include typos, incorrect prop passing, and state update problems.
Adding More Features
While this is a functional notes app, you can enhance it with more features. Here are some ideas:
- Note Styling: Allow users to format their notes with basic markdown or rich text editing.
- Note Highlighting: Implement note highlighting based on keywords or tags.
- Search Functionality: Add a search bar to filter notes.
- Note Categories/Tags: Implement a tagging system to categorize notes.
- Local Storage: Persist notes in the browser’s local storage so they don’t disappear when the page is refreshed.
- Date and Time Stamps: Add timestamps to your notes.
Let’s implement local storage to persist the notes. First, import `useEffect` from React. Update your `App.js` import statement:
import React, { useState, useEffect } from 'react';
Then, add the following `useEffect` hook inside the `App` component to load notes from local storage when the component mounts and save notes to local storage whenever the `notes` state changes:
useEffect(() => {
// Load notes from local storage on component mount
const storedNotes = localStorage.getItem('notes');
if (storedNotes) {
setNotes(JSON.parse(storedNotes));
}
}, []);
useEffect(() => {
// Save notes to local storage whenever notes change
localStorage.setItem('notes', JSON.stringify(notes));
}, [notes]);
Explanation:
- The first `useEffect` hook runs only once, when the component mounts (due to the empty dependency array `[]`). It retrieves the `notes` from local storage (if any) and updates the state.
- The second `useEffect` hook runs whenever the `notes` state changes (due to the `[notes]` dependency array). It saves the current `notes` to local storage.
Common Mistakes and How to Fix Them
Here are some common mistakes beginners make when building React apps, along with solutions:
- Incorrect State Updates: Failing to correctly update state can lead to unexpected behavior. Make sure you are using the correct state update functions (e.g., `setNotes`) and that you are immutably updating the state (using the spread operator, `map`, `filter`, etc.).
- Missing Dependencies in `useEffect`: If you’re using `useEffect` with dependencies, ensure you include all the necessary variables in the dependency array. Omitting dependencies can lead to stale data and unexpected behavior.
- Incorrect Prop Passing: Double-check that you are passing props correctly to child components and that the child components are accessing the props correctly. Typos are a common source of errors.
- Event Handler Binding: When passing event handlers (like `handleAddNote`) to child components, ensure the context is correct, or use arrow functions to bind `this` correctly. In functional components with hooks, this is usually not an issue.
- Ignoring Browser Console Errors: The browser console is your best friend when debugging. Pay attention to error messages and warnings, as they often pinpoint the source of the problem.
Key Takeaways
- You’ve learned how to create a simple React notes app.
- You’ve practiced using functional components and React hooks.
- You’ve gained experience with state management, event handling, and conditional rendering.
- You’ve learned how to implement basic edit and delete functionality.
- You’ve implemented local storage to persist data.
FAQ
Q: How can I add more features to my notes app?
A: You can add features like note styling, search functionality, tagging, and cloud storage integration. Consider using third-party libraries for more advanced features like rich text editing or a database.
Q: How do I handle errors in my React app?
A: Use try-catch blocks in your event handlers and use error boundaries to catch errors during rendering. Consider logging errors to a service like Sentry or Bugsnag to monitor your application’s health.
Q: What is the difference between `useState` and `useEffect`?
A: `useState` is used to manage the state of a component (data that can change). `useEffect` is used to handle side effects, such as data fetching, setting up subscriptions, or interacting with the DOM. `useEffect` can also be used to persist data using local storage.
Q: How do I deploy my React app?
A: You can deploy your React app to platforms like Netlify, Vercel, or GitHub Pages. First, build your app using `npm run build` and then deploy the contents of the `build` folder to your chosen platform.
Next Steps
Building this simple React notes app is a great starting point for your React journey. You can expand on this project by adding more features or trying other beginner projects. The key is to practice consistently and experiment with different React concepts. Consider exploring other React features, such as context, routing, and form validation, to further improve your skills. Remember that the best way to learn is by doing, so keep coding, keep building, and never stop exploring the possibilities of React.
