Welcome folks today in this blog post we will be building a next.js firestore
blog post crud
app using react-quill
wysiwyg editor in browser. All the full source code of the application is shown below.
Get Started
In order to get started you need to create the next.js
app using the below command as shown below
npx create-next-app@latest
Now you need to install the below libraries
using the below command as shown below
npm i firebase
npm i react-quill
And now you will see the final directory
structure of the next.js
app is shown below
Now you need to create the firebaseConfig.js
file for storing the firestore
details about the project as shown below
firebaseConfig.js
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import { initializeApp } from "firebase/app"; import { getFirestore } from 'firebase/firestore' const firebaseConfig = { apiKey: "", authDomain: "", projectId: "", storageBucket: "", messagingSenderId: "", appId: "" }; export const app = initializeApp(firebaseConfig); export const database = getFirestore(app); |
Now you need to replace your own firebase
project data inside the above code
Now we need to edit the index.js
file and copy paste the following code
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
import Head from 'next/head' import styles from '../styles/Evernote.module.scss' import NoteOperations from './components/NoteOperations'; import NoteDetails from './components/NoteDetails'; import { useState } from 'react'; export default function Home() { const [ID, setID] = useState(null); const getSingleNote = (id) => { setID(id) } return ( <div className={styles.container}> <Head> <title>Evernote Clone</title> <meta name="description" content="This is an Evernote Clone" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <div className={styles.container}> <div className={styles.left}> <NoteOperations getSingleNote={getSingleNote} /> </div> <div className={styles.right}> <NoteDetails ID={ID} /> </div> </div> </main> </div> ) } |
As you can see we are including two components
inside our blog
app first one is the component where we will be showing the list of blog posts
and the next component
will be showing the details
of the note
. And also we are importing the useState
hook to declare the state
of the application.
Now you need to create the components
folder inside the root directory and create the NoteOperations.js
file and copy paste the following code
components/NoteOperations.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
import styles from '../../styles/Evernote.module.scss' import { useState, useEffect } from 'react'; import { app, database } from '../../firebaseConfig'; import { collection, addDoc, getDocs } from 'firebase/firestore'; import ReactQuill from 'react-quill'; import 'react-quill/dist/quill.snow.css'; const dbInstance = collection(database, 'notes'); export default function NoteOperations({ getSingleNote }) { const [isInputVisible, setInputVisible] = useState(false); const [noteTitle, setNoteTitle] = useState(''); const [noteDesc, setNoteDesc] = useState(''); const [notesArray, setNotesArray] = useState([]); const inputToggle = () => { setInputVisible(!isInputVisible) } const addDesc = (value) => { setNoteDesc(value) } const saveNote = () => { addDoc(dbInstance, { noteTitle: noteTitle, noteDesc: noteDesc }) .then(() => { setNoteTitle('') setNoteDesc('') getNotes(); }) } const getNotes = () => { getDocs(dbInstance) .then((data) => { setNotesArray(data.docs.map((item) => { return { ...item.data(), id: item.id } })); }) } useEffect(() => { getNotes(); }, []) return ( <> <div className={styles.btnContainer}> <button onClick={inputToggle} className={styles.button}> Add a New Note </button> </div> {isInputVisible ? ( <div className={styles.inputContainer}> <input className={styles.input} placeholder='Enter the Title..' onChange={(e) => setNoteTitle(e.target.value)} value={noteTitle} /> <div className={styles.ReactQuill}> <ReactQuill onChange={addDesc} value={noteDesc} /> </div> <button onClick={saveNote} className={styles.saveBtn}> Save Note </button> </div> ) : ( <></> )} <div className={styles.notesDisplay}> {notesArray.map((note) => { return ( <div className={styles.notesInner} onClick={() => getSingleNote(note.id)}> <h4>{note.noteTitle}</h4> </div> ) })} </div> </> ) } |
As you can see in the above code we are displaying all the notes
from the firestore
database and then we are performing the crud
operations. And here we are using the map()
method to loop through all the notes inside the array and display it inside the browser. And for each note we have assigned the onclick
listener to each note to get the details of the single note
which is selected.
And now when you click the add note
button you will see the simple WYSIWYG
editor using the react-quill
library. And here we will be constructing the blog post
using advanced formatting options as shown below
Now we need to create the NoteDetails.js
component where we will be showing the different details of the blog post where we will be also deleting
the blog post if you press the delete button
NoteDetails.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
import styles from '../../styles/Evernote.module.scss' import { useState, useEffect } from 'react' import { app, database } from '../../firebaseConfig'; import { doc, getDoc, getDocs, collection, updateDoc, deleteDoc } from 'firebase/firestore' import ReactQuill from 'react-quill'; import 'react-quill/dist/quill.snow.css'; const dbInstance = collection(database, 'notes'); export default function NoteDetails({ ID }) { const [singleNote, setSingleNote] = useState({}) const [isEdit, setIsEdit] = useState(false); const [noteTitle, setNoteTitle] = useState(''); const [noteDesc, setNoteDesc] = useState(''); const getSingleNote = async () => { if (ID) { const singleNote = doc(database, 'notes', ID) const data = await getDoc(singleNote) setSingleNote({ ...data.data(), id: data.id }) } } const getNotes = () => { getDocs(dbInstance) .then((data) => { setSingleNote(data.docs.map((item) => { return { ...item.data(), id: item.id } })[0]); }) } const getEditData = () => { setIsEdit(true); setNoteTitle(singleNote.noteTitle); setNoteDesc(singleNote.noteDesc) } useEffect(() => { getNotes(); }, []) useEffect(() => { getSingleNote(); }, [ID]) const editNote = (id) => { const collectionById = doc(database, 'notes', id) updateDoc(collectionById, { noteTitle: noteTitle, noteDesc: noteDesc, }) .then(() => { window.location.reload() }) } const deleteNote = (id) => { const collectionById = doc(database, 'notes', id) deleteDoc(collectionById) .then(() => { window.location.reload() }) } return ( <> <div> <button className={styles.editBtn} onClick={getEditData} >Edit </button> <button className={styles.deleteBtn} onClick={() => deleteNote(singleNote.id)} >Delete </button> </div> {isEdit ? ( <div className={styles.inputContainer}> <input className={styles.input} placeholder='Enter the Title..' onChange={(e) => setNoteTitle(e.target.value)} value={noteTitle} /> <div className={styles.ReactQuill}> <ReactQuill onChange={setNoteDesc} value={noteDesc} /> </div> <button onClick={() => editNote(singleNote.id)} className={styles.saveBtn}> Update Note </button> </div> ) : ( <></> )} <h2>{singleNote.noteTitle}</h2> <div dangerouslySetInnerHTML={{ __html: singleNote.noteDesc }}></div> </> ) } |
As you can see in the above code we are able to edit
the note or blog post when we click it. And we are doing this editing
in an editor. And also we are deleting the blog post
using the deleteDoc()
method and inside this method we are passing the id
of the blog post that we want to delete it.
GITHUB REPOSITORY