Today, people have many activities to do, also a lot of great ideas or projects in their mind then we usually use our pencil to write those thoughts on paper quickly because they won't be lost easily. Unfortunately, we usually don't have a pencil or paper where we can put all ideas. Therefore, a notes app will be helpful.
Pre-requisites
- Node.js (>= 14 version) installed.
- Have a Firebase project (without any database) created.
- Have basic React.js & Firebase knowledge.
- Have basic Font Awesome knowledge.
Content
- Design the app
- Create our database in Cloud Firestore
- Create our React app and install some npm dependencies
- Prepare our React app with Firebase
- Create a hook for getting notes from Firebase
- Create NoteActionCard component
- Create NoteForm component
- Create NoteHeader component
- Create NoteList component
- Put the NoteList component in our app
- Demo
- Next steps
Design the app
Our notes app will have features like add a new note, delete a note, also use CSS Flexbox, Grid Layout, and integrate with Firebase to store our notes.
Create our database in Cloud Firestore
Cloud Firestore is a cloud-hosted, NoSQL database that your iOS, Android, and web apps can access directly via native SDKs - Google
- Structure our database.
We'll create a collection notes with many documents (note).
For example:
[
{
"id": "6URepJtDX3tytzEzPqng",
"text": "Write my article on Hashnode",
"createdAt": "April 6, 2021 at 3:28:55 AM UTC-4"
},
{
"id": "0AUaYnC3hm95NZ89WrBI",
"text": "Call to my friend",
"createdAt": "April 7, 2021 at 3:28:55 AM UTC-4"
},
{
"id": "6LmlgsV54X9jmPgsT7Yi",
"text": "Go to meeting on Saturday",
"createdAt": "April 8, 2021 at 3:28:55 AM UTC-4"
}
]
Create our React app and install some npm dependencies
npx create-react-app notes-app
cd notes-app
npm i firebase
npm i @fortawesome/react-fontawesome
npm i @fortawesome/fontawesome-svg-core
npm i @fortawesome/free-solid-svg-icons
Prepare our React app with Firebase
We need to create a new file where we'll put our Firebase configuration: firebaseConfig.js
. (Note.- You need to replace your Firebase credentials here.)
// notes-app/src/firebaseConfig.js
import firebase from "firebase/app";
import "firebase/firestore";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "notes-app-bedc0.firebaseapp.com",
projectId: "notes-app-bedc0",
storageBucket: "notes-app-bedc0.appspot.com",
messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxx"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();
export { db };
Create a hook for getting notes from Firebase
From React 16.8, we can use Hooks also we can create our custom hooks only we need to create a function with the prefix use
.
Now, we create our custom hook useReadNotes
for this purpose we'll "extract" some logic using hooks useState
and useEffect
to get information about notes from Firebase.
// notes-app/src/hooks/useReadNotes.js
import { db } from '../firebaseConfig'
const { useState, useEffect } = require("react");
function useReadNotes() {
const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const firebaseNotes = [];
setLoading(true);
db.collection("notes")
.orderBy("createdAt", "desc")
.get()
.then((querySnapshot) => {
querySnapshot.docs.forEach((doc) => {
firebaseNotes.push({
id: doc.id,
...doc.data()
});
});
setNotes(firebaseNotes);
setLoading(false);
});
}, []);
return [loading, notes, setNotes];
}
export default useReadNotes;
Create NoteActionCard component
NoteActionCard
will help us to open a small form where we can put information about a note.
// notes-app/src/components/NoteActionCard.jsx
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons'
const NoteActionCard = ({ setIsVisibleNew }) => {
const openNewNoteForm = () => setIsVisibleNew(true);
return (
<div className="note note--empty">
<p className="note__text note__text--add">Add new note</p>
<button
className="note__button note__button--new"
onClick={openNewNoteForm}
>
<FontAwesomeIcon className="note__icon" icon={faPlus} />
</button>
</div>
)
}
export default NoteActionCard;
Create NoteForm component
This component that we will use to enter information about a note.
// notes-app/src/components/NoteForm.jsx
import { db } from '../firebaseConfig'
import { useState } from "react"
const NoteForm = ({ setNotes, setIsVisibleNew, setSaving }) => {
const [text, setText] = useState("");
const handletext = (e) => setText(e.target.value);
const cancelNote = () => setIsVisibleNew(false);
const [error, setError] = useState(null);
const saveNote = (e) => {
e.preventDefault();
setSaving(true);
// Add a new document in collection "notes"
let newNote = { text, createdAt: new Date() };
db.collection("notes")
.add({ text, createdAt: new Date() })
.then(({ id }) => {
newNote.id = id;
setNotes(prevNotes => [newNote, ...prevNotes])
setIsVisibleNew(false);
setSaving(false);
})
.catch((error) => {
console.error("Error writing document: ", error);
setError(error);
});
}
return (
<div className="note note--create">
<h2 className="note__heading">New Note</h2>
<form onSubmit={saveNote} className="note__form">
<div className="note__field">
<label
htmlFor="note__input"
className="note__label">Text for your note</label>
<input type="text"
className="note__input"
name="note__input"
id="note__input"
value={text}
onChange={handletext}
required
/>
</div>
<div className="note__buttons">
<button type="submit"
className="note__button note__button--create">Create</button>
<button type="submit"
className="note__button note__button--cancel"
onClick={cancelNote}>Cancel</button>
</div>
</form>
{error && <p className="note__message--error">{error}</p>}
</div>
)
}
export default NoteForm;
Create NoteHeader component
With NoteHeader
we can delete a note (โ).
// notes-app/src/components/NoteHeader.jsx
import { db } from '../firebaseConfig'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTimesCircle } from '@fortawesome/free-solid-svg-icons'
const NoteHeader = ({ id, setNotes }) => {
const deleteNote = () => {
db.collection("notes").doc(id).delete().then(() => {
console.log("Note successfully deleted!");
setNotes(prevNotes => prevNotes.filter(note => note.id !== id))
}).catch((error) => {
console.error("Error removing note: ", error);
});
};
return (
<div className="note__header">
<button
className="note__button"
onClick={deleteNote}
>
<FontAwesomeIcon icon={faTimesCircle} />
</button>
</div>
)
}
export default NoteHeader;
Create NoteList component
NodeList
is a section where we will show all notes registered.
// notes-app/src/components/NoteList.jsx
import { useState } from "react";
import useReadNotes from "../hooks/useReadNotes"
import NoteActionCard from "./NoteActionCard";
import NoteForm from "./NoteForm";
import NoteHeader from './NoteHeader';
const NoteList = () => {
const [loading, notes = [], setNotes] = useReadNotes();
const [isVisibleNew, setIsVisibleNew] = useState(false);
const [saving, setSaving] = useState(false);
return (
<section className="notes">
<NoteActionCard
setIsVisibleNew={setIsVisibleNew}
/>
{isVisibleNew &&
<>
<NoteForm
setNotes={setNotes}
setIsVisibleNew={setIsVisibleNew}
setSaving={setSaving}
/>
{saving && <p className="note__message">Saving new note...</p>}
</>
}
{loading &&
<p className="notes__loading">Notes are loading...</p>
}
{
notes.map(({ id, text = "" }) =>
<div className="note note--show" key={id} id={id}>
<NoteHeader
id={id}
setNotes={setNotes}
/>
<div className="note__body">
<p className="note__text">{text}</p>
</div>
</div>
)
}
</section>
)
}
export default NoteList;
Put the NoteList component in our app
With all components built, we can use them in the main component App
.
import './App.css';
import NoteList from './components/NoteList';
function App() {
return (
<div className="App">
<h1>Notes</h1>
<NoteList />
</div>
);
}
export default App;
Also, we need add our CSS styles:
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;700&family=Roboto:wght@100;300;400;500;700&display=swap");
:root {
--primary: #370617;
--secondary: hsl(92, 24%, 70%);
--tertiary: #e7b287;
--accent: #081c15;
}
* {
margin: 0;
padding: 0;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
html {
box-sizing: border-box;
}
body {
background-color: var(--primary);
color: var(--secondary);
font-family: "Open Sans", sans-serif;
}
button {
appearance: none;
background-color: transparent;
border: none;
display: block;
outline: none;
text-align: center;
}
input:focus {
outline: none;
}
h1,
h2 {
font-family: "Roboto", sans-serif;
font-weight: 700;
text-align: center;
}
h1 {
margin: 0.5em 0;
}
i {
color: var(--secondary);
}
section {
padding: 0 0.5em;
}
.notes {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.note {
border-radius: 0.5em;
color: var(--accent);
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.5em;
padding: 0.5em;
}
.note--create {
background-color: var(--tertiary);
}
.note--empty {
align-items: center;
border: 1px dashed var(--secondary);
color: var(--secondary);
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.5em;
padding: 4em;
}
.note--show {
background-color: var(--secondary);
}
.note__body {
padding: 4em;
}
.note__button {
color: var(--accent);
}
.note__button--create {
background-color: var(--primary);
color: var(--secondary);
}
.note__button--create,
.note__button--cancel {
border: 1px solid var(--primary);
border-radius: 0.5em;
padding: 0.5em;
text-align: center;
width: 100%;
}
.note__button--new {
border-radius: 50%;
color: var(--secondary);
font-size: 1.2em;
text-align: center;
width: 2em;
}
.note__buttons {
display: flex;
flex-direction: column;
gap: 0.2em;
}
.note__form {
display: flex;
flex-direction: column;
gap: 0.5em;
}
.note__header {
display: flex;
flex-direction: row;
justify-content: flex-end;
gap: 0.5em;
}
.note__input {
border: none;
border-radius: 0.5em;
padding: 0.5em;
width: 100%;
}
.note__text {
text-align: center;
}
@media (min-width: 350px) {
.notes {
display: grid;
grid-template-columns: repeat(3, 1fr);
margin: 0 auto;
}
}
Demo
Next steps
- Search notes by text.
- Edit a note.
Thanks for your reading and time! I appreciate it.
See you until the next article!๐