This post is use to demonstrate the use of CRUD operations in react using react.
CRUD stands for create/read/update/delete.
We also learn the following
- Persist data using local storage
- debouncing
- props
- hooks
- Date formatter function
Following is the structure of the app
This app has 3 components Sidebar, Note Container, Notes.
App component will be the main component/parent component. All the signals pass through this component only. In another words all the children component talk to each other through app component.
First Lets create the bere bone of the project
SideBar Js
import React from 'react'
import './Sidebar.css'
export default function Sidebar() {
return (
<h1>Sidebar</h1>
)
}
SideBar css
.sidebar-container {
height: 100%;
width: 10%;
display: flex;
flex-direction: column;
align-items: center;
}
svg {
margin-bottom: 10px;
}
ul>li {
height: 30px;
width: 30px;
border-radius: 50%;
margin: 5px;
list-style: none;
}
Note Container js
import React from 'react'
import './NoteContainer.css'
export default function NoteContainer() {
return (
<h1>Note Container</h1>
)
}
Note Container css
.note-container {
height: 100%;
}
.header {
margin-bottom: 2rem;
}
.note-body {
display: flex;
flex-wrap: wrap;
gap: 20px;
overflow: auto;
height: 90%;
}
Notes Js
import React from 'react'
import './Note.css'
function Note() {
return (
<h1>Notes</h1>
)
}
export default Note
Notes css
.card-container {
height: 300px;
width: 250px;
border-radius: 1rem;
}
.text-area {
width: 100%;
height: 80%;
border: none;
outline: none;
resize: none;
line-height: 1.875rem;
padding: 1rem;
background-color: transparent;
}
.footer {
display: flex;
align-items: center;
justify-content: space-around;
}
App js
import './App.css';
function App() {
return (
<div className="App">
<Sidebar />
<NoteContainer />
</div>
);
}
export default App;
App css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.App {
height: 100vh;
display: flex;
}
.custom-scroll::-webkit-scrollbar {
width: 8px;
}
.custom-scroll::-webkit-scrollbar-track {
background-color: transparent;
}
.custom-scroll::-webkit-scrollbar-thumb {
border-radius: 20px;
background-color: lightgray;
transition: 300ms;
}
.custom-scroll::-webkit-scrollbar-thumb:hover {
background-color: gray;
}
First lets create the sidebar
import React from 'react'
import './Sidebar.css'
export default function Sidebar() {
const color = ['#fe9b72', '#fec971', "#00d4fe", '#b693fd', '#e4ee91']
const [open, setOpen] = useState(false);
return (
<div className='sidebar-container'>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" class="w-6 h-6" height='100' width='100'
onClick={() => setOpen(!open)}>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{open &&
<ul>
{color.map((item, index) => <li key={index} style={{ background: item }} onClick={() => props.addnotes(item)}></li>)}
</ul>
}
</div>
)
}
Wow! What just happened. I know that's a lot to process. Don't worry I'll walk you through this. Oh! also keep your eyes open you might find some useful code snippet along the way that you use again and again almost in every project 😉.
Now lets see the jsx first.
I did nothing fancy hear just created a parent div with class sidebar-container. See css above in starting. Then added a svg image to show icon. That's it.
If you look closely 🧐, you might see a onClick() function in svg tag. What does it do you might ask.
constt [open, setOpen] => useState(false);
<svg onClick ={() => setOpen(!open)}
Well all it does it toggle the div or image or whatever you want to toggle. Like on/off the light. Think as you want something to render on screen only when click on a button and then disappear when you click again. Well this function do the trick. When you click on the button with which we attached this function, it toggles the state from true to false or vice versa.
Now your second question might be what the duck is
{open &&
<ul>
{color.map((item, index) => <li key={index} style={{ background: item }} onClick={() => props.addnotes(item)}></li>)}
</ul>
}
This is nothing but a way to tell the computer if state is true render that ul tag else do not render. This is called conditional rendering in world of react and && operator will be used to implement this magic. You can achieve this using class as shown below. Also I am using array of different colors which are mapped as a li list. This will used to produce notes component of different color.
<ul className={`sidebar_list ${open ? 'sidebar_list_active' : ''} `}>
{
color.map((item, index) => <li className='sidebar_list_item' key={index} style={{ backgroundColor: item }} onClick={() => props.addNotes(item)} />)
}
</ul>
You also need a pinch of css here to make it work. Note that classname are not same because it was copied from other project.
.sidebar {
display: flex;
flex-direction: column;
gap: 40px;
}
.sidebar svg {
cursor: pointer;
}
.sidebar_list {
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
height: 0;
}
.sidebar_list_active {
height: 300px;
}
.sidebar_list_item {
list-style-type: none;
height: 26px;
width: 26px;
border-radius: 50%;
}
Wallah! Done and Dustin. See the preview below
Lets structure Notes component first
All I am doing is structure the jsx to see how it looks like. Remember it is very important to fist create the design and fill it with dummy data. It will be easier to imagine the solution.
import React from 'react'
import './Note.css'
function Note() {
return (
<div className='card-container' style={{ backgroundColor: 'cyan' }}>
<textarea className='text-area' defaultValue={'asdasdsadas'}</textarea>
<div className='footer'>
<p> 16:26 PM 01 Nov</p>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" height='25' width='25'>
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</div>
</div>
)
}
export default Note
This will show like this
Now that we see the design we can imagine how data will come in this jsx. Structure of data should be like below
{
text: 'ssasdasda',
time: '16:26 PM 01 Nov',
Structure Note Container component
import React from 'react'
import Note from '../Note/Note'
import './NoteContainer.css'
export default function NoteContainer(props) {
return (
<div className='note-container'>
<h1 className='header'>Notes</h1>
<div className='note-body custom-scroll'>
<Note/>
</div>
</div>
)
}
Notice that we are calling the Notes component in Note Container. All the notes will leave in Notes Container.
Now that the basic image is clear. We will work with dummy data and functionality along the way. I am gonna demonstrate how data travel through component in react. Remember , It is very important to understand props and states in react as it's the basic of mostly all react apps. I will also demonstrate how data propagate from children to parent in react. This is very important concept to learn. You must have clarity on callbacks in js.
Demonstration of props with dummy data
import './App.css';
import NoteContainer from './components/NoteContainer/NoteContainer';
import Sidebar from './components/Sidebar/Sidebar';
import { useState, useEffect } from 'react'
function App() {
const notes=[
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'yellow'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'pink'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'orange'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'seagreen'
}]
return (
<div className="App">
<Sidebar />
<NoteContainer notes={notes} />
</div>
);
}
export default App;
//----notes array will send to note container as props---
import React from 'react'
import Note from '../Note/Note'
import './NoteContainer.css'
export default function NoteContainer(props) {
return (
<div className='note-container'>
<h1 className='header'>Notes</h1>
<div className='note-body custom-scroll'>
<Note notes={props.notes} />
</div>
</div>
)
}
//----from notes-container to notes data will travel---
import React from 'react'
import './Note.css'
function Note(props) {
return (
<div className='card-container' style={{ backgroundColor: props.notes.color }}>
<textarea className='text-area' defaultValue= {props.notes.text}></textarea>
<div className='footer'>
<p> {props.notes.time}</p>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" height='25' width='25'>
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</div>
</div>
)
}
export default Note
Functionality to create new notes
So we have to send signal from sidebar to notes component to create new note. App component will be the mediator. You will learn how child component send signals to parent component.
import './App.css';
import NoteContainer from './components/NoteContainer/NoteContainer';
import Sidebar from './components/Sidebar/Sidebar';
import { useState, useEffect } from 'react'
function App() {
const [notes, setNotes] = useState([
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'yellow'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'pink'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'orange'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'seagreen'
}])
const addNotes = (color) => {
const tempNotes = [...notes];
tempNotes.push({
id: Date.now(),
text: '',
time: Date.now();
color,
})
setNotes(tempNotes);
}
return (
<div className="App">
<Sidebar addNotes={addNotes}/>
<NoteContainer notes={notes} />
</div>
);
}
We will get this function as props in sidebar component
Lets understand the functionality by an image
I'm hoping this image help you to understand what's going on here.
I'll first wrap this in bullets.
- Create a add function in app component.
- This component will return a object which will added to the main array of notes, which is stored in state.
- This array is traveling to the Note container component and note container sending this data to Notes component.
- Notes component fetching required data from array and feeding to the designated design field. I hope you get the idea.
Functionality to delete a note
Delete from ui is very simple. Believe it or not it just a one liner.
import './App.css';
import NoteContainer from './components/NoteContainer/NoteContainer';
import Sidebar from './components/Sidebar/Sidebar';
import { useState, useEffect } from 'react'
function App() {
const [notes, setNotes] = useState([
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'yellow'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'pink'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'orange'
},
{
text: 'vdscmdscdc',
time: '4.37 PM',
color: 'seagreen'
}])
const addNotes = (color) => {
const tempNotes = [...notes];
tempNotes.push({
id: Date.now(),
text: '',
time: Date.now();
color,
})
setNotes(tempNotes);
}
const deleteNote = id => {
//You can use two method to delete element
// array filter method or array slice method
tempNotes = [...notes];
setNotes(tempNotes.filter(note => note.id !== id);
/*
using slicemethod
const index = tempNotes.findIndex(note => note.id === id);
if(!index) return;
setNotes(tempNotes.slice(index,1));
*/
}
return (
<div className="App">
<Sidebar addNotes={addNotes}/>
<NoteContainer notes={notes} deleteNotes={deleteNotes}/>
</div>
);
}
//----------Note container will get delete function as props and send it to Notes component
import React from 'react'
import Note from '../Note/Note'
import './NoteContainer.css'
export default function NoteContainer(props) {
return (
<div className='note-container'>
<h1 className='header'>Notes</h1>
<div className='note-body custom-scroll'>
{
props.notes.length > 0 ? notes.map((item) => (
<Note key={item.id} note={item} deleteNote={props.deleteNote}
/>
)) : <h1>Empty Notes</h1>
}
</div>
</div>
)
}
//--------deleteNote function pass to the delete icon as callback function
import React from 'react'
import './Note.css'
function Note(props) {
return (
<div className='card-container' style={{ backgroundColor: props.note.color }}>
<textarea className='text-area' defaultValue={props.note.text} onChange={e => updateText(e.target.value, props.note.id)}></textarea>
<div className='footer'>
<p>{props.note.time}</p>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" height='25' width='25' onClick={() => props.deleteNote(props.note.id)}>
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</div>
</div>
)
}
export default Note
Also I added the conditional rendering in Note container component. If the length of the notes array is greater then 0 it will show notes else it will show 'Empty Notes' massage.
Persist Notes and text in local storage and update text
Also this includes format data, useEffect hooks, debounce **
**Plz write your queries in comments
//App
import './App.css';
import NoteContainer from './components/NoteContainer/NoteContainer';
import Sidebar from './components/Sidebar/Sidebar';
import { useState, useEffect } from 'react'
function App() {
const [notes, setNotes] = useState(JSON.parse(localStorage.getItem('note-app')) || [])
const addnotes = (color) => {
const tempNotes = [...notes];
tempNotes.push({
id: Date.now() + '' + Math.floor(Math.random() * 78),
text: '',
time: Date.now(),
color
})
setNotes(tempNotes);
}
const deleteNote = (id) => {
const tempNotes = [...notes];
// const index = tempNotes.findIndex(item => item.id === id);
// if(index <=0) return
//setNotes(tempNotes.splice(index,1))
setNotes(tempNotes.filter(item => item.id !== id));
}
useEffect(() => {
localStorage.setItem('note-app', JSON.stringify(notes));
})
const updateText = (text, id) => {
const tempNotes = [...notes];
const index = tempNotes.findIndex(item => item.id === id);
if (index <= 0) return
tempNotes[index].text = text;
setNotes(tempNotes);
}
return (
<div className="App">
<Sidebar addnotes={addnotes} />
<NoteContainer notes={notes} deleteNote={deleteNote} updateText={updateText} />
</div>
);
}
export default App;
//Sidebar
import React, { useState } from 'react'
import './Sidebar.css'
export default function Sidebar(props) {
const color = ['#fe9b72', '#fec971', "#00d4fe", '#b693fd', '#e4ee91']
const [open, setOpen] = useState(false);
return (
<div className='sidebar-container'>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" class="w-6 h-6" height='100' width='100' onClick={() => setOpen(!open)}>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v6m3-3H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
{open &&
<ul>
{color.map((item, index) => <li key={index} style={{ background: item }} onClick={() => props.addnotes(item)}></li>)}
</ul>
}
</div>
)
}
//Note Container
import React from 'react'
import Note from '../Note/Note'
import './NoteContainer.css'
export default function NoteContainer(props) {
const reverseArray = (arr) => {
const array = [];
for (let i = arr.length - 1; i >= 0; i--) {
array.push(arr[i]);
}
return array
}
const notes = reverseArray(props.notes)
return (
<div className='note-container'>
<h1 className='header'>Notes</h1>
<div className='note-body custom-scroll'>
{
notes.length > 0 ? notes.map((item) => (
<Note key={item.id} note={item} deleteNote={props.deleteNote}
updateText={props.updateText} />
)) : <h1>Empty Notes</h1>
}
</div>
</div>
)
}
// Notes
import React from 'react'
import './Note.css'
function Note(props) {
const formatDate = (value) => {
if (!value) return;
const date = new Date(value);
const monthsName = ['Jan', 'Feb', 'March', 'April', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec'];
let hrs = date.getHours();
let amPm = hrs > 12 ? 'PM' : 'AM';
hrs = hrs ? hrs : '12';
hrs = hrs > 24 ? hrs - 12 : hrs;
let min = date.getMinutes();
min = min < 10 ? '0' + min : min;
let day = date.getDate();
day = day < 10 ? '0' + day : day;
const month = monthsName[date.getMonth()];
return `${hrs}:${min} ${amPm} ${day} ${month}`;
};
let timer = 500, timeout;
const debounce = (fun) => {
clearTimeout(timeout);
timeout = setTimeout(fun, timer);
}
const updateText = (text, id) => {
debounce(() => props.updateText(text, id))
};
return (
<div className='card-container' style={{ backgroundColor: props.note.color }}>
<textarea className='text-area' defaultValue={props.note.text} onChange={e => updateText(e.target.value, props.note.id)}></textarea>
<div className='footer'>
<p>{formatDate(props.note.time)}</p>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6" height='25' width='25' onClick={() => props.deleteNote(props.note.id)}>
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</div>
</div>
)
}
export default Note
Top comments (0)