Building a Sticky Notes Application with `ait` - A Step-by-Step Guide
Introduction
Welcome to this comprehensive guide on creating a dynamic sticky notes application! In this chapter, we will embark on a journey to build a feature-rich notes app leveraging modern web technologies. Our toolkit includes React for a responsive and interactive user interface and ait, an innovative open-source, self-hosted Backend-as-a-Service (BaaS) platform.
Open Source: Software with source code that is publicly accessible, allowing anyone to view, modify, and distribute it. This promotes collaboration, transparency, and community-driven development.
Backend-as-a-Service (BaaS): A cloud service model that provides developers with pre-built backend functionalities like databases, user authentication, and push notifications, simplifying backend development and allowing focus on the frontend.
ait
will serve as our robust and flexible backend, handling data storage and management seamlessly. This chapter will guide you through each stage of development, from setting up your environment to implementing advanced features like autosaving and drag-and-drop functionality. We will also explore helpful resources, including written guides and source code, to ensure a smooth and rewarding learning experience.
Let’s dive in and begin building our sticky notes application!
1. Project Overview and Setup
Before we begin coding, let’s understand the features of our final sticky notes application and prepare our development environment.
1.1 Application Demo and Features
Our application will be a visually appealing and functional sticky notes interface with the following key features:
- Dynamic Note Creation: Users can easily add new sticky notes to the workspace.
- Drag and Drop Functionality: Notes can be freely moved around the screen, offering a flexible and intuitive organization.
- Color Customization: A color palette will allow users to personalize each note’s appearance.
- Real-time Autosave: Notes are automatically saved to a database in the background, ensuring no data loss, even upon page refresh.
- Automatic Note Resizing: Notes will dynamically adjust their size to accommodate the content, providing a seamless user experience.
- Note Deletion: Users can easily remove notes they no longer need.
- Database Persistence: All note data, including content, color, and position, will be stored in a real database powered by
ait
, ensuring data persistence across sessions.
1.2 Helpful Resources: Source Code and Written Guide
To facilitate your learning and development process, we have prepared valuable resources:
- Final Source Code: The complete source code for the sticky notes application is available for download. This serves as an invaluable reference, allowing you to examine the finished product and troubleshoot any issues you may encounter. The source code is linked in the video description accompanying this chapter.
- Comprehensive Written Guide: A detailed, step-by-step written guide accompanies this chapter. This guide mirrors the video tutorial, providing:
- Detailed Instructions: Clear, written explanations for each step of the development process.
- Code Snippets: Ready-to-copy code snippets that you can directly integrate into your project.
- Visual Aids: Screenshots and visual cues to help you follow along.
- Reference Points: Easy navigation to specific sections, allowing you to quickly jump to relevant information.
This written guide is designed to be followed alongside this chapter, offering an alternative learning pathway and a readily accessible resource for code examples and instructions. The written guide is also linked in the video description.
2. Frontend Setup with React and Vite
Let’s begin by setting up our frontend environment using React, a popular JavaScript library for building user interfaces, and Vite, a fast and efficient build tool.
React: A declarative, efficient, and flexible JavaScript library for building user interfaces or UI components. It allows developers to create reusable UI components and manage application state effectively.
Vite: A build tool that aims to provide a faster and leaner development experience for modern web projects. It is known for its rapid cold start and hot module replacement.
2.1 Creating a React Application with Vite
-
Prerequisites: Ensure you have Node.js installed on your system. Node.js is a JavaScript runtime environment that is essential for running JavaScript code outside of a web browser.
Node.js: A JavaScript runtime environment that executes JavaScript code server-side. It’s built on Chrome’s V8 JavaScript engine and enables developers to use JavaScript for backend development and command-line tools.
-
Create Vite Project: Open your terminal or command prompt and navigate to your desired project directory. Run the following command:
npm create vite
-
Project Configuration: Vite will prompt you with a series of questions:
- Project Name: Enter a name for your project (e.g.,
my-sticky-notes-app
). - Framework: Select
React
. - Variant: Choose
JavaScript
(orTypeScript
if you prefer, though this guide will use JavaScript).
- Project Name: Enter a name for your project (e.g.,
-
Install Dependencies: Navigate into your newly created project directory:
cd my-sticky-notes-app
Then, install the project dependencies using npm (Node Package Manager):
npm (Node Package Manager): A package manager for the JavaScript programming language. It is the default package manager for Node.js and allows developers to easily install, manage, and share JavaScript packages and libraries.
npm install
-
Start Development Server: Launch the development server to preview your application in the browser:
npm run dev
Your application should now be running at
http://localhost:5173/
(or a similar port).
2.2 Cleaning Up Boilerplate Code
To start with a clean slate, we will remove unnecessary boilerplate files and code:
-
Delete
App.css
: In thesrc
folder, delete theApp.css
file. -
Clear
index.css
: Openindex.css
in thesrc
folder and remove all existing content. We will add our custom styles here. -
Clean Up
App.jsx
: OpenApp.jsx
in thesrc
folder and modify it as follows:import './index.css'; function App() { return ( <> <div>My App</div> </> ); } export default App;
This removes the default React content and sets up a basic component structure.
2.3 Creating the NotesPage
Component
To organize our application, we will create a NotesPage
component to house the main application logic:
-
Create
pages
Folder: Inside thesrc
folder, create a new folder namedpages
. -
Create
NotesPage.jsx
: Inside thepages
folder, create a new file namedNotesPage.jsx
. -
Implement
NotesPage
Component: Add the following code toNotesPage.jsx
:import React from 'react'; function NotesPage() { return ( <div> Notes Page </div> ); } export default NotesPage;
-
Import
NotesPage
inApp.jsx
: UpdateApp.jsx
to import and render theNotesPage
component:import './index.css'; import NotesPage from './pages/NotesPage'; function App() { return ( <> <NotesPage /> </> ); } export default App;
Now, your browser should display “Notes Page”.
2.4 Basic Styling in index.css
Let’s add some basic global styles to index.css
to set the foundation for our application’s visual appearance:
body {
background-color: #1e293b; /* Dark background color */
font-family: sans-serif;
color: white; /* Default text color */
font-size: 16px;
margin: 0; /* Remove default body margin */
padding: 0; /* Remove default body padding */
}
#root {
background-image: url("data:image/svg+xml,%3Csvg width='42' height='44' viewBox='0 0 42 44' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' fill='none' fill-rule='evenodd'%3E%3Cg id='ic_pentagon' fill='%239C92AC' fill-rule='nonzero'%3E%3Cpath d='M40 22L31.5 40.7 12.5 40.7 4 22 12.5 3.3 31.5 3.3zM38 22L29.9 39.1 13.9 39.1 6 22 13.9 4.9 29.9 4.9z' id='Shape'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); /* Subtle grid background */
height: 100vh; /* Full viewport height */
background-size: 20px 20px; /* Size of the grid pattern */
position: relative; /* Needed for absolute positioning of notes */
}
These styles set a dark background, default font, text color, and a subtle grid pattern for the application’s root element. The position: relative;
on #root
is crucial for the absolute positioning of our sticky notes later.
3. Creating the Note Card Component and Static Data
Now we will create the NoteCard
component to represent individual sticky notes and introduce static data for initial development.
3.1 Setting Up Fake Data
For development purposes, we’ll create a fakeData.js
file with sample note data. This allows us to work on the frontend components without immediately connecting to the backend.
-
Create
assets
Folder: In thesrc
folder, create a new folder namedassets
. -
Create
fakeData.js
: Inside theassets
folder, create a new file namedfakeData.js
. -
Add Fake Data: Paste the following JavaScript code into
fakeData.js
:export const fakeData = [ { id: '1', body: JSON.stringify("First note with some rich text\n\n- List item 1\n- List item 2"), colors: JSON.stringify({ body: '#fef08a', header: '#fde047', text: '#000000', }), position: JSON.stringify({ x: 20, y: 20 }), }, { id: '2', body: JSON.stringify("Second note - simple text"), colors: JSON.stringify({ body: '#bae6fd', header: '#7dd3fc', text: '#000000', }), position: JSON.stringify({ x: 200, y: 80 }), }, { id: '3', body: JSON.stringify("Another note with a longer text to test auto-sizing of the note. This should wrap and expand the note vertically."), colors: JSON.stringify({ body: '#d8b4fe', header: '#c084fc', text: '#ffffff', }), position: JSON.stringify({ x: 350, y: 150 }), }, ];
This
fakeData
array contains objects representing sticky notes. Each note object includes:-
id
: A unique identifier for the note. -
body
: The content of the note, stringified as JSON to allow for rich text formatting (like line breaks). -
colors
: An object defining colors for the note’s body, header, and text, also stringified as JSON. -
position
: An object defining the initialx
andy
coordinates of the note, stringified as JSON.
JSON (JavaScript Object Notation): A lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate. It is often used for transmitting data in web applications.
The use of
JSON.stringify()
ensures that complex data structures like objects can be stored as strings and later parsed back into objects when needed. -
3.2 Creating the NoteCard
Component
Now, let’s create the NoteCard
component to render individual notes:
-
Create
components
Folder: In thesrc
folder, create a new folder namedcomponents
. -
Create
NoteCard.jsx
: Inside thecomponents
folder, create a new file namedNoteCard.jsx
. -
Implement
NoteCard
Component: Add the following code toNoteCard.jsx
:import React from 'react'; import './NoteCard.css'; // We will create this CSS file later function NoteCard({ note }) { const body = JSON.parse(note.body); // Parse the body string back to text return ( <div className="card"> {body} </div> ); } export default NoteCard;
This component receives a
note
object as a prop and parses thebody
string back into plain text usingJSON.parse()
. It then renders thebody
content within adiv
with the class name “card”.
3.3 Rendering NoteCard
Components in NotesPage
Let’s integrate the NoteCard
component into NotesPage
to display our fake data:
-
Import
fakeData
andNoteCard
inNotesPage.jsx
: UpdateNotesPage.jsx
as follows:import React from 'react'; import { fakeData as notes } from '../assets/fakeData'; // Import fake data import NoteCard from '../components/NoteCard'; // Import NoteCard component function NotesPage() { return ( <div> {notes.map((note) => ( <NoteCard key={note.id} note={note} /> // Render NoteCard for each note ))} </div> ); } export default NotesPage;
We import
fakeData
asnotes
and theNoteCard
component. We then usenotes.map()
to iterate over thenotes
array and render aNoteCard
component for each note object. Thekey={note.id}
prop is essential for React to efficiently update and re-render list items.
4. Styling the Note Card
Now, let’s enhance the visual appearance of our NoteCard
component with CSS.
4.1 Basic Card Styling in index.css
Add the following CSS rules to index.css
to style the basic card structure:
.card {
width: 250px; /* Default width of the note */
border-radius: 8px;
padding: 10px;
cursor: pointer; /* Indicate draggable */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Subtle shadow */
position: absolute; /* For drag and drop positioning */
}
These styles define the width, border radius, padding, cursor, box shadow, and position: absolute;
for the .card
class. The position: absolute;
is crucial for positioning notes dynamically on the screen.
4.2 Applying Inline Styles for Dynamic Colors and Positioning in NoteCard.jsx
Modify NoteCard.jsx
to apply dynamic styles based on the note
data:
import React from 'react';
function NoteCard({ note }) {
const body = JSON.parse(note.body);
const colors = JSON.parse(note.colors); // Parse colors object
const position = JSON.parse(note.position); // Parse position object
return (
<div
className="card"
style={{
backgroundColor: colors.body, // Dynamic background color
top: position.y + 'px', // Dynamic top position
left: position.x + 'px', // Dynamic left position
}}
>
{body}
</div>
);
}
export default NoteCard;
We now parse the colors
and position
strings from the note
prop using JSON.parse()
. We then use inline styles within the div
element to dynamically set the backgroundColor
, top
, and left
styles based on the parsed colors
and position
objects.
4.3 Enhancing Note Card Style: Header, Body, and Text Area
Let’s further refine the NoteCard
component to resemble a realistic sticky note with a header and an editable text area.
-
Update
NoteCard.jsx
Structure: Modify theNoteCard
component structure inNoteCard.jsx
:import React from 'react'; import './NoteCard.css'; // Ensure CSS file is imported function NoteCard({ note }) { const body = JSON.parse(note.body); const colors = JSON.parse(note.colors); const position = JSON.parse(note.position); return ( <div className="card" style={{ backgroundColor: colors.body, top: position.y + 'px', left: position.x + 'px', }} > <div className="card-header" style={{ backgroundColor: colors.header }} // Header background color > {/* Header content will go here */} </div> <div className="card-body"> <textarea className="card-body-textarea" defaultValue={body} // Set default value to note body style={{ color: colors.text }} // Text color /> </div> </div> ); } export default NoteCard;
We’ve added:
- A
div
with the classcard-header
to represent the note’s header. - A
div
with the classcard-body
to contain the editable text area. - A
<textarea>
element with the classcard-body-textarea
to allow users to edit the note content directly. ThedefaultValue
prop sets the initial text content to the parsedbody
.
- A
-
Add CSS for Card Header and Body in
index.css
: Add the following CSS rules toindex.css
:.card-header { background-color: #fde047; /* Default header color */ padding: 8px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; /* For header content alignment */ justify-content: space-between; /* Distribute header content */ align-items: center; /* Center items vertically in header */ } .card-body { padding: 10px; border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; } .card-body-textarea { background-color: inherit; /* Inherit background from card-body */ border: none; /* Remove textarea border */ width: 100%; /* Take full width of card-body */ height: 100%; /* Take full height of card-body */ resize: none; /* Disable textarea resizing */ font-family: inherit; /* Inherit font from body */ font-size: 1em; /* Inherit font size */ outline: none; /* Remove focus outline */ } .card-body-textarea:focus { outline: none; /* Remove focus outline on textarea */ }
These styles define the appearance of the card header and body, including padding, border radii, and styling for the text area to make it blend seamlessly into the note card. Crucially,
.card-body-textarea
styles remove the default textarea appearance, making it look like plain text within the note.
4.4 Adding the Trash Icon
Let’s add a trash icon to the note header to enable note deletion.
-
Create
Trash.jsx
inicons
Folder: Create a new folder namedicons
in thesrc
folder and inside it create a file namedTrash.jsx
. -
Implement
Trash
Icon Component: Add the following code toTrash.jsx
:import React from 'react'; function Trash() { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6" style={{ width: '20px', height: '20px', cursor: 'pointer' }} > <path fillRule="evenodd" d="M16.5 5.25a.75.75 0 00-1.5 0v2.25h-3V5.25a.75.75 0 00-1.5 0v2.25h-3V5.25a.75.75 0 00-1.5 0v2.25H5.25a.75.75 0 000 1.5h1.125v9a2.25 2.25 0 002.25 2.25h8.25a2.25 2.25 0 002.25-2.25v-9H18a.75.75 0 000-1.5h-2.625V5.25zM7.5 8.25h9v9a.75.75 0 01-.75.75H8.25a.75.75 0 01-.75-.75v-9z" clipRule="evenodd" /> </svg> ); } export default Trash;
This component renders an SVG icon representing a trash can.
-
Import and Render
Trash
Icon inNoteCard.jsx
: Import theTrash
component and render it within thecard-header
inNoteCard.jsx
:import React from 'react'; import './NoteCard.css'; import Trash from '../icons/Trash'; // Import Trash icon function NoteCard({ note }) { // ... (previous code) return ( <div className="card" style={/* ... */}> <div className="card-header" style={/* ... */}> <Trash /> {/* Render Trash icon in header */} </div> {/* ... (card-body) */} </div> ); } export default NoteCard;
Now, each note card will display a trash icon in its header.
5. Implementing Drag and Drop Functionality
Let’s make our sticky notes draggable, allowing users to reposition them on the screen.
5.1 Absolute Positioning and State Management for Note Position
To enable drag and drop, we need to manage the position of each note dynamically using React state.
-
Import
useState
anduseRef
inNoteCard.jsx
: Import the necessary hooks:import React, { useState, useRef, useEffect } from 'react'; // Import useState and useRef // ... (other imports)
useState Hook: A React Hook that allows you to add state variables to functional components. It returns a state variable and a function to update that variable.
useRef Hook: A React Hook that allows you to create mutable references to DOM elements or any JavaScript values that persist across renders without causing re-renders when changed.
-
Initialize Position State and Card Ref in
NoteCard
: Inside theNoteCard
component, initialize state for position and create a ref for the card element:function NoteCard({ note }) { // ... (previous code) const initialPosition = JSON.parse(note.position); const [position, setPosition] = useState(initialPosition); // Position state const cardRef = useRef(null); // Ref for the card element const mouseStartPositionX = useRef(0); // Ref to store initial mouse X position const mouseStartPositionY = useRef(0); // Ref to store initial mouse Y position // ... (rest of the component) }
We initialize
position
state with the parsednote.position
and createcardRef
usinguseRef(null)
.mouseStartPositionX
andmouseStartPositionY
refs are used to track the initial mouse position when dragging starts. -
Update Inline Styles to Use State Position: Modify the inline styles in
NoteCard.jsx
to use theposition
state:<div className="card" style={{ backgroundColor: colors.body, top: position.y + 'px', // Use state position left: position.x + 'px', // Use state position }} ref={cardRef} // Attach the ref to the card div > {/* ... (rest of the card) */} </div>
We replace
position.y
andposition.x
withposition.y
andposition.x
from the state, and attach thecardRef
to thediv
element.
5.2 Implementing Mouse Event Handlers for Dragging
Let’s add event handlers to the card-header
to initiate and handle dragging:
-
Add
onMouseDown
Handler tocard-header
: InNoteCard.jsx
, add theonMouseDown
event handler to thecard-header
:<div className="card-header" style={{ backgroundColor: colors.header }} onMouseDown={handleMouseDown} // Add onMouseDown handler > <Trash /> </div>
-
Implement
handleMouseDown
Function: Add thehandleMouseDown
function within theNoteCard
component:function NoteCard({ note }) { // ... (previous state and refs) const handleMouseDown = (e) => { mouseStartPositionX.current = e.clientX; // Store initial mouse X mouseStartPositionY.current = e.clientY; // Store initial mouse Y document.addEventListener('mousemove', handleMouseMove); // Add mousemove listener document.addEventListener('mouseup', handleMouseUp); // Add mouseup listener }; const handleMouseMove = (e) => { // Mouse move logic will be implemented here }; const handleMouseUp = (e) => { // Mouse up logic will be implemented here }; // ... (return statement) }
The
handleMouseDown
function is triggered when the mouse button is pressed down on thecard-header
. It:- Stores the initial mouse
clientX
andclientY
coordinates in themouseStartPositionX
andmouseStartPositionY
refs. - Attaches
mousemove
andmouseup
event listeners to thedocument
. This is crucial because we want to track mouse movements even when the mouse is moved outside thecard-header
element during dragging.
- Stores the initial mouse
-
Implement
handleMouseMove
Function: Add thehandleMouseMove
function to update the note’s position during dragging:function NoteCard({ note }) { // ... (previous code) const handleMouseMove = (e) => { const mouseMoveDirectionX = e.clientX - mouseStartPositionX.current; // X direction of mouse movement const mouseMoveDirectionY = e.clientY - mouseStartPositionY.current; // Y direction of mouse movement mouseStartPositionX.current = e.clientX; // Update start X for next move event mouseStartPositionY.current = e.clientY; // Update start Y for next move event setPosition((prevPosition) => ({ x: prevPosition.x + mouseMoveDirectionX, // Update X position based on mouse movement y: prevPosition.y + mouseMoveDirectionY, // Update Y position based on mouse movement })); }; // ... (handleMouseDown and handleMouseUp) }
The
handleMouseMove
function is triggered repeatedly as the mouse moves while the mouse button is pressed. It:- Calculates the
mouseMoveDirectionX
andmouseMoveDirectionY
, representing the change in mouse position since the lastmousemove
event. - Updates
mouseStartPositionX.current
andmouseStartPositionY.current
to the current mouse position, preparing for the nextmousemove
event calculation. - Calls
setPosition
to update theposition
state. It uses a functional update to ensure it’s based on the previous state (prevPosition
). The newx
andy
positions are calculated by adding themouseMoveDirectionX
andmouseMoveDirectionY
to the previousx
andy
positions respectively.
- Calculates the
-
Implement
handleMouseUp
Function: Add thehandleMouseUp
function to remove the event listeners when dragging ends:function NoteCard({ note }) { // ... (previous code) const handleMouseUp = (e) => { document.removeEventListener('mousemove', handleMouseMove); // Remove mousemove listener document.removeEventListener('mouseup', handleMouseUp); // Remove mouseup listener }; // ... (handleMouseDown and handleMouseMove) }
The
handleMouseUp
function is triggered when the mouse button is released. It removes themousemove
andmouseup
event listeners from thedocument
, stopping the dragging behavior.
With these event handlers in place, you should now be able to drag and drop the sticky notes around the screen!
6. Setting Bounds for Dragging
Currently, notes can be dragged completely off-screen. Let’s implement bounds to prevent notes from being dragged too far to the top and left.
6.1 Creating the setNewOffset
Utility Function
Create a utility function to calculate and enforce boundaries for note positions.
-
Create
utils.js
insrc
Folder: Create a new folder namedutils
in thesrc
folder and inside it create a file namedutils.js
. -
Implement
setNewOffset
Function: Add the following code toutils.js
:export function setNewOffset(card, mouseMoveDirection) { const offsetX = card.offsetLeft - (mouseMoveDirection?.x || 0); // Calculate potential new left offset const offsetY = card.offsetTop - (mouseMoveDirection?.y || 0); // Calculate potential new top offset return { x: offsetX < 0 ? 0 : offsetX, // Ensure left offset is not negative y: offsetY < 0 ? 0 : offsetY, // Ensure top offset is not negative }; }
This function takes the
card
element andmouseMoveDirection
object as input. It calculates the potential newoffsetX
andoffsetY
based on the mouse movement. It then returns an object withx
andy
coordinates, ensuring that neither coordinate is less than 0. If a coordinate is calculated to be negative (meaning the note is being dragged beyond the top or left boundary), it is set to 0, effectively stopping the note from moving further in that direction.
6.2 Integrating setNewOffset
in NoteCard.jsx
Modify the handleMouseMove
function in NoteCard.jsx
to use the setNewOffset
function:
-
Import
setNewOffset
inNoteCard.jsx
: Import the utility function:import React, { useState, useRef, useEffect } from 'react'; import { setNewOffset } from '../utils/utils'; // Import setNewOffset // ... (other imports)
-
Update
handleMouseMove
Function: Modify thehandleMouseMove
function to usesetNewOffset
:function NoteCard({ note }) { // ... (previous code) const handleMouseMove = (e) => { const mouseMoveDirectionX = e.clientX - mouseStartPositionX.current; const mouseMoveDirectionY = e.clientY - mouseStartPositionY.current; mouseStartPositionX.current = e.clientX; mouseStartPositionY.current = e.clientY; const newPosition = setNewOffset(cardRef.current, { // Calculate bounded position x: mouseMoveDirectionX, y: mouseMoveDirectionY, }); setPosition(newPosition); // Set bounded position to state }; // ... (handleMouseDown and handleMouseUp) }
In
handleMouseMove
, we now callsetNewOffset
, passing incardRef.current
and themouseMoveDirection
object. The returnednewPosition
object, which contains the boundedx
andy
coordinates, is then used to update theposition
state.
Now, when you drag the notes, they will stop at the top and left edges of the viewport, preventing them from being dragged completely off-screen.
7. Z-Index Management for Active Notes
When notes overlap, we want the currently active note (the one being dragged or clicked) to always appear on top. We can achieve this using z-index.
z-index: A CSS property that controls the stacking order of positioned elements. Elements with a higher z-index value appear in front of elements with lower z-index values.
7.1 Creating the setZIndex
Utility Function
Create a utility function to manage the z-index of notes, ensuring the active note is always on top.
-
Implement
setZIndex
Function inutils.js
: Add the following function toutils.js
:export function setZIndex(selectedCard) { const cards = Array.from(document.getElementsByClassName('card')); // Get all card elements cards.forEach((card) => { if (card !== selectedCard) { // If the card is not the selected card card.style.zIndex = parseInt(selectedCard.style.zIndex) - 1; // Set z-index lower than selected card } }); selectedCard.style.zIndex = 1000; // Set z-index of selected card to a high value }
This function:
- Gets all elements with the class name “card” using
document.getElementsByClassName
and converts the HTMLCollection to an Array usingArray.from
. - Iterates through each
card
element. - If the
card
is not theselectedCard
(the currently active note), it sets itsz-index
to be one less than thez-index
of theselectedCard
. - Sets the
z-index
of theselectedCard
to a high value (1000 in this case), ensuring it is brought to the front.
- Gets all elements with the class name “card” using
7.2 Integrating setZIndex
in NoteCard.jsx
Modify the handleMouseDown
function in NoteCard.jsx
to call the setZIndex
function:
-
Import
setZIndex
inNoteCard.jsx
: Import the utility function:import React, { useState, useRef, useEffect } from 'react'; import { setNewOffset, setZIndex } from '../utils/utils'; // Import setZIndex // ... (other imports)
-
Update
handleMouseDown
Function: CallsetZIndex
inhandleMouseDown
:function NoteCard({ note }) { // ... (previous code) const handleMouseDown = (e) => { if (e.target.className === 'card-header') { // Check if click is on header setZIndex(cardRef.current); // Call setZIndex to bring card to front mouseStartPositionX.current = e.clientX; mouseStartPositionY.current = e.clientY; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } }; // ... (handleMouseMove and handleMouseUp) }
In
handleMouseDown
, we now callsetZIndex
, passing incardRef.current
to bring the clicked note to the front. We also added a conditionif (e.target.className === 'card-header')
to ensure thatsetZIndex
is only called when the click originates from thecard-header
, preventing accidental z-index changes when clicking within the note body.
Now, when you click on a note or start dragging it, it will always be brought to the front, ensuring proper stacking order when notes overlap.
(The transcript ends abruptly here. A comprehensive educational text would continue with the remaining features, backend integration, autosave, delete, context API, and adding new notes, following the same structured and detailed approach.)
Chapter: Enhancing Note Application Functionality: Color Customization and State Management
Introduction
This chapter details the process of enhancing a note-taking application by implementing color customization for individual notes and refining the application’s state management. We will explore techniques for dynamically adjusting the visual hierarchy of notes and enabling users to personalize their notes with different colors. This chapter will cover component creation, state management using context, event handling, and data persistence.
1. Addressing Note Stacking Order: Ensuring Active Note Visibility
Initially, when new notes were added to the application, or when notes became active, there was an issue with their visual stacking order. To rectify this, we implemented a solution using the z-index
CSS property.
1.1 Utilizing z-index
for Active Note Prioritization
The goal was to ensure that the most recently interacted-with note, considered the “active” note, would visually appear on top of other notes. This was achieved by dynamically adjusting the z-index
of the note element.
- Implementing
setZindex
Function: A function,setZindex
, was created to handle the dynamic adjustment of thez-index
property. This function would be responsible for applying a higherz-index
value to the active note. - Using
useEffect
Hook: To trigger thesetZindex
function at the appropriate time, the ReactuseEffect
hook was employed.
**useEffect Hook:** A React Hook that lets you perform side effects in functional components. It serves the same purpose as `componentDidMount`, `componentDidUpdate`, and `componentWillUnmount` in React classes, but unified into a single API.
* The `useEffect` hook was configured to be called when the note component renders. This ensures that the `z-index` adjustment occurs immediately when a note is displayed.
- Accessing DOM Element with
ref.current
: To manipulate the DOM element of the note component directly and apply thez-index
style, aref
was used.
**ref.current:** In React, when you create a ref using `React.createRef()` or `useRef()`, the `.current` property holds the DOM element or component instance that the ref is attached to. It provides a way to access and interact with the underlying DOM or component.
* `cardRef.current` was used to access the DOM node associated with the note card component.
- Applying
setZindex
on Note Rendering: ThesetZindex
function was called within theuseEffect
hook, targetingcardRef.current
. This ensured that upon rendering each note, itsz-index
was appropriately set. - Resolution of Active State Issue: By implementing this
z-index
adjustment usinguseEffect
, the application now correctly displays active notes on top of others, resolving the initial stacking order problem. When a new note is added, it is brought to the front, and subsequent notes are correctly stacked above previously added notes, enhancing the user experience by clearly indicating the active note.
2. Implementing Note Color Customization: Personalizing Notes
The next major enhancement involved enabling users to customize the color of individual notes. This feature allows for visual organization and personalization within the note-taking application.
2.1 Creating the Reusable Color
Component
To implement color customization, a reusable Color
component was created. This component represents a single color option that users can select to apply to a note.
- Component Creation: A new component named
Color
was generated within the application’s component directory.
**Component:** In programming, particularly in UI frameworks like React, a component is a reusable, self-contained building block that encapsulates a piece of user interface and its behavior. Components can be composed together to build complex UIs.
- Passing Color as a Prop: The
Color
component was designed to receive color information as a prop namedcolor
.
**Prop (Properties):** In React and other component-based frameworks, props are inputs to components. They are data passed down from a parent component to a child component to customize its rendering and behavior.
* This `color` prop is expected to be an object containing color details, such as a `colorHeader` property, as defined in a `colors` array within the application's assets.
onClick
Event Handler -changeColor
Function: TheColor
component was made interactive by adding anonClick
event handler.
**onClick Event Handler:** A function that is executed when a user clicks on an HTML element. In JavaScript and UI frameworks, it's a common way to handle user interactions and trigger actions in response to clicks.
* When a `Color` component is clicked, the `changeColor` function is triggered. This function was initially designed to simply log the color information to the console for verification.
- Inline Styling for Color Display: To visually represent each color option, inline styling was applied to the
Color
component.
**Inline Styling:** A method of applying CSS styles directly to HTML elements using the `style` attribute. In React, inline styles are typically provided as JavaScript objects.
* The `style` attribute was used to set the `backgroundColor` of the `Color` component to the `color.colorHeader` value passed as a prop. This dynamically sets the background color of each color option based on its defined color.
- CSS Styling for Dimensions and Hover Effect: To ensure the color components were visible and interactive, CSS styling was added.
**CSS Classes:** Reusable styles defined in a CSS stylesheet that can be applied to HTML elements by referencing the class name in the element's `class` attribute. This promotes organized and maintainable styling.
* CSS classes named `color` and a hover effect were defined in the application's CSS file (`index.css`). These styles likely set the width, height, and hover behavior of the color components, making them visually distinct and interactive.
2.2 Integrating Color Selection into the Controls
Component
With the Color
component created, the next step was to render these color options within the application’s Controls
component. The Controls
component serves as a container for interactive elements related to note manipulation, such as color selection.
- Importing Necessary Modules: To use the
Color
component and the color data, necessary modules were imported into theControls
component.
**Import:** In programming, particularly in modular systems like JavaScript modules, the `import` statement is used to bring in functionalities (variables, functions, classes, or modules) from other files or modules so they can be used in the current file.
* `colors` array: This likely contains the color data defined in `assets`, which will be used to generate the color options.
* `Color` component: The custom `Color` component created in the previous step.
- Mapping Through the
colors
Array: To render aColor
component for each color option, themap
method was used on thecolors
array.
**Map (Array Method):** A higher-order function in JavaScript that iterates over each element in an array and applies a provided function to each element, returning a new array with the results of calling the function on every element. In React, it is commonly used to render lists of components.
* `colors.map()`: This iterates through each element in the `colors` array.
* For each color in the `colors` array:
* A `Color` component is rendered.
* A unique `key` prop is provided to each `Color` component.
**Key Prop:** A special prop in React that should be used when rendering lists of components. Keys help React identify which items have changed, are added, or are removed. They are essential for efficient list rendering and updating, especially when dealing with dynamic lists. Keys should be unique among siblings.
* `key={color.id}`: The `color.id` is used as the key, ensuring each `Color` component in the list has a unique identifier.
* The `color` object itself is passed as a prop to the `Color` component.
- Rendering Color Components in
Controls
: By mapping through thecolors
array and rendering aColor
component for each color, theControls
component now displays a series of color options. These color components are visually represented based on the CSS styles and the inline styles applied within theColor
component itself.
2.3 Managing Selected Note State with Context
To implement the color change functionality, it was necessary to track which note is currently selected by the user. This “selected note” state is crucial for applying the chosen color to the correct note. To manage this state effectively across different components, React Context was utilized.
- Lifting State to Context: The “selected note” state was “lifted” to a higher level in the component hierarchy, specifically into a Context Provider.
**Lifted State:** In component-based UI frameworks like React, "lifting state" refers to moving state up the component tree to a common ancestor component. This is done when multiple components need to share or modify the same state. By lifting state, it becomes accessible to all components within that part of the tree.
* This makes the selected note information accessible to both the `NoteCard` (where notes are rendered and selected) and the `Color` component (where color selection occurs).
- Creating
selectedNote
State inNotesProvider
: Within theNotesProvider
component (likely responsible for managing note-related state), a new state variableselectedNote
was created using theuseState
hook.
**State:** In user interface programming, state refers to the data that describes the current condition or situation of a component or application. State is dynamic and can change over time, often in response to user interactions or events. Managing state effectively is crucial for building interactive and responsive applications.
* `const [selectedNote, setSelectedNote] = useState(null);`: This initializes the `selectedNote` state to `null`, indicating that initially no note is selected.
**Null:** A primitive value in JavaScript that represents the intentional absence of any object value. It is often used to indicate that a variable or property has no value or that an object does not exist.
- Providing
selectedNote
andsetSelectedNote
via Context: TheselectedNote
state and its setter functionsetSelectedNote
were provided through the Note Context.
**Context (React Context API):** A feature in React that allows you to share state and functions (values) with components deep down in the component tree without having to manually pass props through every level. It's useful for data that is considered "global" for a tree of React components, such as current user, theme, or language.
**Provider (Context Provider):** A component provided by React's Context API that makes context values available to all consuming components that are descendants of this provider. It wraps a part of the component tree and supplies the context value.
* This allows any component within the `NotesProvider`'s scope to access and update the `selectedNote` state.
- Updating
selectedNote
inNoteCard
Component: TheNoteCard
component was modified to update theselectedNote
state when a note is interacted with.- Importing
useContext
andnoteContext
: TheuseContext
hook and thenoteContext
were imported into theNoteCard
component. - Accessing
setSelectedNote
from Context: ThesetSelectedNote
function was extracted from the Note Context usinguseContext(noteContext)
. - Setting
selectedNote
ononMouseDown
andonFocus
Events: Event handlers were added to the header of theNoteCard
to trigger thesetSelectedNote
function.
- Importing
**onFocus Event:** An event that occurs when an HTML element gains focus, typically triggered when the user clicks or tabs onto an input field, or when an element is programmatically focused using JavaScript.
**onMouseDown Event:** An event that occurs when a mouse button is pressed down over an HTML element. It is triggered when the mouse button is initially pressed, before it is released.
* `onMouseDown`: This event handler was added to the note header to capture clicks on the header as a selection trigger.
* `onFocus`: This event handler was already in place (likely for handling note editing focus) and was also used to set the `selectedNote`.
* In both event handlers, `setSelectedNote(note)` is called, passing the entire `note` object as the new `selectedNote` state. This ensures that when a user interacts with a note's header (either by clicking or focusing), that specific note is set as the `selectedNote` in the application's state.
- Verifying
selectedNote
inColor
Component: To confirm that theselectedNote
state was being updated correctly, theColor
component was modified to log theselectedNote
to the console.
**console.log():** A JavaScript function that is used to write messages to the console, typically for debugging purposes. It's a fundamental tool for developers to inspect values, track code execution, and diagnose issues in their code.
* **Accessing `selectedNote` from Context:** The `useContext` hook and `noteContext` were also imported into the `Color` component.
* **Logging `selectedNote`:** `console.log(selectedNote)` was added within the `changeColor` function in the `Color` component. Now, when a color is clicked, the currently selected note (if any) is logged to the console, allowing for verification of the state management implementation.
2.4 Implementing Color Change Logic and Database Persistence
With the selectedNote
state in place, the final step was to implement the actual color change logic and persist these changes to the database. This involves updating the note’s color data in the application’s state and then synchronizing these changes with the backend database.
- Handling Color Change in
changeColor
Function: ThechangeColor
function in theColor
component was expanded to implement the color update logic.- Error Handling for No Selected Note: A
try...catch
block (though described as using analert
in the transcript, suggesting a simplified approach) was used to handle the case where no note is selected when a color is clicked.
- Error Handling for No Selected Note: A
**try...catch Statement:** A control flow statement in programming languages like JavaScript used for exception handling. The `try` block encloses code that might throw an error, and the `catch` block specifies code to handle any error that occurs within the `try` block, preventing the program from crashing and allowing for graceful error management.
* If `selectedNote` is `null` (no note selected), an alert message "You must select a note before changing colors" is displayed to the user. An `if` statement could also have been used for this check.
**if Statement:** A conditional statement in programming that executes a block of code only if a specified condition is true. It allows programs to make decisions and execute different code paths based on whether a condition is met.
* **Finding the Index of the Selected Note:** If a note is selected, the next step is to find the index of the selected note within the `notes` array in the application's state.
* `notes.findIndex()` method was used for this purpose.
**findIndex (Array Method):** A method in JavaScript arrays that returns the index of the first element in the array that satisfies a provided testing function. If no element satisfies the function, it returns -1.
* `const currentNoteIndex = notes.findIndex((note) => note.id === selectedNote.id);`: This line of code iterates through the `notes` array and checks if the `id` of each `note` matches the `id` of the `selectedNote`. If a match is found, the index of that note is returned and stored in `currentNoteIndex`.
* **Creating an Updated Note Object:** Once the index is found, an updated note object is created with the new color information.
* **Spread Operator for Object Copying:** The spread operator (`...`) was used to create a shallow copy of the original note object.
**Spread Operator (...):** In JavaScript, the spread operator has several uses. When used with objects or arrays, it allows you to expand or unpack the elements of an iterable (like an array) or properties of an object into another array or object. It's often used for creating copies of arrays or objects or combining them.
* `const updatedNote = { ...notes[currentNoteIndex] };`: This creates a new object `updatedNote` that initially contains all the properties of the original note at `currentNoteIndex` in the `notes` array.
* **Updating `colors` Property:** The `colors` property of the `updatedNote` object is updated with the newly selected color.
* `updatedNote.colors = JSON.stringify(color.color);`: This line sets the `colors` property of the `updatedNote` object. The `color.color` (presumably containing the color data of the clicked color component) is stringified using `JSON.stringify`.
**JSON.stringify():** A method in JavaScript that converts a JavaScript value (object or array) to a JSON string. JSON (JavaScript Object Notation) is a lightweight data-interchange format that is easy for humans to read and write and easy for machines to parse and generate.
* **Creating a New `notes` Array with Updated Note:** A new array `newNotes` is created by copying the original `notes` array and then replacing the note at `currentNoteIndex` with the `updatedNote`.
* **Spread Operator for Array Copying:** The spread operator is again used to create a copy of the `notes` array.
* `const newNotes = [...notes];`: This creates a new array `newNotes` that is a copy of the `notes` array.
* `newNotes[currentNoteIndex] = updatedNote;`: This line replaces the note at `currentNoteIndex` in the `newNotes` array with the `updatedNote` object, effectively updating the color information for that specific note in the new array.
* **Updating State with `setNotes`:** The application's state is updated by calling `setNotes` with the `newNotes` array.
* `setNotes(newNotes);`: This updates the `notes` state with the `newNotes` array, reflecting the color change in the application's UI.
- Persisting Changes to Database: To make the color changes persistent, the updated note data is sent to the database.
- Database Update Function:
db.notes.update()
function (or similar, depending on the database interaction library) is called to update the note in the database.
- Database Update Function:
**Database Update:** The process of modifying existing data records in a database to reflect changes in the application's state or user actions. This ensures data persistence, meaning the changes are saved and will be available even after the application is closed and reopened.
* `db.notes.update(selectedNote.id, { colors: JSON.stringify(color.color) });`: This line calls the `update` function of the `db.notes` database interface.
* `selectedNote.id`: The ID of the selected note is passed as the first argument, identifying which note to update in the database.
* `{ colors: JSON.stringify(color.color) }`: An object is passed as the second argument, representing the payload of data to update.
**Payload:** In the context of data transmission or APIs, the payload refers to the actual data being sent, excluding headers and metadata. It's the part of the message that contains the information the sender wants to convey to the receiver.
* `colors: JSON.stringify(color.color)`: This specifies that the `colors` field in the database record should be updated with the stringified `color.color` data.
- Testing and Verification: After implementing the color change logic and database update, the functionality was tested by selecting notes, changing their colors, and then refreshing the application to verify that the color changes persisted. This confirmed that the color customization feature was working correctly and that the changes were being saved to the database.
Conclusion
This chapter detailed the implementation of two key enhancements to a note-taking application: resolving note stacking order issues and adding color customization. By utilizing z-index
and useEffect
, the application now effectively manages the visual hierarchy of notes. Furthermore, by creating a reusable Color
component, managing selected note state with React Context, and implementing color change logic with database persistence, users can now personalize their notes with different colors, significantly improving the application’s usability and user experience.
Further Exploration
Building upon the features implemented in this chapter, further enhancements could include:
- More sophisticated color palettes: Expanding the range of available colors and potentially allowing users to create custom color palettes.
- User-specific color preferences: Storing user color preferences and applying them across sessions.
- Integration with note content: Exploring ways to tie color to note content, such as using color to categorize notes or highlight important information.
- Advanced state management patterns: Investigating more complex state management solutions like Redux or Zustand for larger applications with more intricate state requirements.