1/1/1970
Create src/components/Sidebar.js
import React from 'react';
const Sidebar = () => {
return (
// Sidebar
<div className="app-sidebar">
{/* Section 1: Sidebar Header */}
<div className="app-sidebar-header">
<h1>Gkmeena Notes</h1>
<button>Add</button>
</div>
{/* Section 2: Sidebar Notes */}
<div className="app-sidebar-notes">
<div className="app-sidebar-note">
{/* Section 2.1: Sidebar Notes Title */}
<div className="sidebar-note-title">
<strong>Title</strong>
<button>Delete</button>
</div>
{/* Section 2.2: Sidebar Notes Preview */}
<p>Note Preview</p>
<small className="note-meta">Last modified [date]</small>
</div>
</div>
</div>
);
}
export default Sidebar;src/components/Main.js
import React from "react";
const Main = () => {
return (
// Main Section
<div className="app-main">
{/*Section 1: Main Note Edit*/}
<div className="app-main-note-edit">
<input type="text" id="title" autoFocus /> {/*input & Autofocus ⭐*/}
<textarea id="body" placeholder="Write your note here..." /> {/*TextArea ⭐*/}
</div>
{/*Section 3: Main Note Preview*/}
<div className="app-main-note-preview">
<h1 className="preview-title">TITLE</h1>
<div className="markdown-preview">note preview</div>
</div>
</div>
);
};
export default Main;App.js ComponentUpdated Create src/App.js
import React, { act } from "react";
import Sidebar from "./components/Sidebar";
import Main from "./components/Main";
import { useState } from "react";
import uuid from "react-uuid"; // library for random ID ⭐
function App() {
// Notes State Array
const [notes, setNotes] = useState([]); // Array State for Notes ⭐
const [activeNote, setActiveNote] = useState(false); // State (initialy boolean=false) for Active or not ⭐
// Note: All Event handling function will be Arrow function.
const onAddNote = () => {
const newNote = {
id: uuid(), // random 'uuid' for the object ⭐
title: "Untitled Note",
body: "",
lastModified: Date.now(), // Javascript function for current date ⭐
};
setNotes([newNote, ...notes]); // Append an object in State Array using spreadoperator⭐
};
const onDeleteNote = (idToDelete) => {
setNotes(notes.filter((note) => note.id !== idToDelete)); // Filter Array and Save to set States ⭐
};
return (
<div className="App">
{/* Sidebar Component*/}
<Sidebar
notes={notes} //Notes State Prop⭐
onAddNote={onAddNote}
onDeleteNote={onDeleteNote}
activeNote={activeNote} // activeNote State Prop⭐
setActiveNote={setActiveNote} // activeNotes set State Prop⭐
/>
{/* Main Component*/}
<Main/>
</div>
);
}
export default App;Updated Create src/components/Sidebar.js
import React from 'react';
const Sidebar = ({notes, onAddNote, onDeleteNote, activeNote, setActiveNote}) => { // Props Taken from `App.js`
return (
// Sidebar
<div className="app-sidebar">
{/* Section 1: Sidebar Header */}
<div className="app-sidebar-header">
<h1>Notes</h1>
<button onClick={onAddNote}>Add</button> {/*onDeleteNote() use*/}
</div>
{/* Section 2: Sidebar Notes */}
<div className="app-sidebar-notes">
{notes.map((note)=> // Map Function for Dynamically Use Notes
<div className={`app-sidebar-note ${note.id === activeNote && "active" /* Dynamically return "active" if condition true note: using template literal for js logic*/}`} onClick={()=>setActiveNote(note.id)}> {/* setActiveNote() to show current Note Active*/}
{/* Section 2.1: Sidebar Notes Title */}
<div className="sidebar-note-title">
<strong>{note.title}</strong> {/*using props.note title*/}
<button onClick={()=>onDeleteNote(note.id)}>Delete</button> {/*onDeleteNote() use, Note: onDelete()❌ -> ()=>onDelete() ✔️ ⭐*/}
</div>
{/* Section 2.2: Sidebar Notes Preview */}
<p>{note.body && (note.body.length>100? note.body.substr(0,100)+"..." : note.body)}</p> {/* Using props.note body and only show upto 100 string if greater ⭐*/}
<small className="note-meta">{new Date(note.lastModified).toLocaleDateString("en-GB", { hour: "2-digit", minute:"2-digit"})}</small> {/* show note.lastModified to date format and also minute and Hour ⭐*/}
</div>
)}
</div>
</div>
);
}
export default Sidebar;App.js ComponentUpdated src/App.js
import React from "react";
import Sidebar from "./components/Sidebar";
import Main from "./components/Main";
import { useState } from "react";
import uuid from "react-uuid"; // library for random ID ⭐
function App() {
// Notes State Array
const [notes, setNotes] = useState([]); // Array State for Notes ⭐
const [activeNote, setActiveNote] = useState(false); // State (initialy boolean=false) for Active or not ⭐
// Note: All Event handling function will be Arrow function.
const onAddNote = () => {
const newNote = {
id: uuid(), // random 'uuid' for the object ⭐
title: "Untitled Note",
body: "",
lastModified: Date.now(), // Javascript function for current date ⭐
};
setNotes([newNote, ...notes]); // Append an object in State Array using spreadoperator⭐
};
const onDeleteNote = (idToDelete) => {
setNotes(notes.filter((note) => note.id !== idToDelete)); // Filter Array and Save to set States ⭐
};
// Get Active Helper function to get the current stored id
const getActiveNote = () => {
return notes.find((note)=> note.id===activeNote); // activeNote is set by seActive Note inside Sidebar.js ⭐
}
// Update Note
const onUpdateNote = (updatedNote) => {
const updatedNotesArray = notes.map((note)=>{ // Map function to update Notes array ⭐
if(note.id === activeNote){
return updatedNote; // update only for activeNote in newarray ⭐
}
return note;
});
setNotes(updatedNotesArray) // set State to Updated returned Note Array ⭐
}
return (
<div className="App">
{/* Sidebar Component*/}
<Sidebar
notes={notes} //Notes State Prop⭐
onAddNote={onAddNote}
onDeleteNote={onDeleteNote}
activeNote={activeNote} // activeNote State Prop⭐
setActiveNote={setActiveNote} // activeNotes set State Prop⭐
/>
{/* Main Component*/}
<Main
// Why not Directly passing activeNote ⭐❓
activeNote={getActiveNote()} // getActiveNote() automatically running a function as a prop and update activeNote ⭐
onUpdateNote={onUpdateNote} // Run only when called
/>
</div>
);
}
export default App;Updated src/components/Main.js
import React from "react";
const Main = ({activeNote, onUpdateNote}) => { /* but activeNote is not taken*/
const onEditField = (key, value) => {
onUpdateNote({
...activeNote, // spread operator: creates a shallow copy of all properties of the activeNote object and spreads them into another object or array.
// Key= title/body is Dynamically typed and shown
[key]: value, // Set value to key
lastModified: Date.now() // Update date
// Note: Id need not to update, so don't write here.
})
}
if(!activeNote) return <div className="no-active-note">No note Selected</div> // If activeNote = false
return (
// Main Section
<div className="app-main">
{/*Section 1: Main Note Edit*/}
<div className="app-main-note-edit">
<input type="text" id="title" autoFocus
value={activeNote.title} // Initial Value
onChange={(e)=> onEditField("title", e.target.value)} // onEditField() to show title text change ⭐
/>
<textarea id="body" placeholder="Write your note here..."
value={activeNote.body} // Initial Value
onChange={(e)=> onEditField("body", e.target.value)} // onEditField() to show body text changes ⭐
/>
</div>
{/*Section 3: Main Note Preview*/}
<div className="app-main-note-preview">
<h1 className="preview-title">{activeNote.title}</h1>
<div className="markdown-preview">{activeNote.body}</div>
</div>
</div>
);
};
export default Main;import React from "react";
import ReactMarkdown from "react-markdown";
const Main = ({activeNote, onUpdateNote}) => { /* but activeNote is not taken*/
const onEditField = (key, value) => {
onUpdateNote({
...activeNote, // spread operator: creates a shallow copy of all properties of the activeNote object and spreads them into another object or array.
// Key= title/body is Dynamically typed and shown
[key]: value, // Set value to key
lastModified: Date.now() // Update date
// Note: Id need not to update, so don't write here.
});
}
if(!activeNote) return <div className="no-active-note">No note Selected</div> // If activeNote = false
return (
// Main Section
<div className="app-main">
{/*Section 1: Main Note Edit*/}
<div className="app-main-note-edit">
<input type="text" id="title" autoFocus
value={activeNote.title} // Initial Value
onChange={(e)=> onEditField("title", e.target.value)} // onEditField() to show title text change ⭐
/>
<textarea id="body" placeholder="Write your note here..."
value={activeNote.body} // Initial Value
onChange={(e)=> onEditField("body", e.target.value)} // onEditField() to show body text changes ⭐
/>
</div>
{/*Section 3: Main Note Preview*/}
<div className="app-main-note-preview">
<h1 className="preview-title">{activeNote.title}</h1>
{/* <div className="markdown-preview">{activeNote.body}</div> */}
{/*Replace the <div></div> to <ReactMarkdown></ReactMarkdown> to Render in Markdown*/}
<ReactMarkdown className="markdown-preview">{activeNote.body}</ReactMarkdown>
</div>
</div>
);
};
export default Main;import React from 'react';
const Sidebar = ({notes, onAddNote, onDeleteNote, activeNote, setActiveNote}) => { // Props Taken from `App.js`
// function using complicated sorting algorithm to sort based on lastModified date
const sortedNotes = notes.sort((a,b)=>b.lastModified-a.lastModified);
return (
// Sidebar
<div className="app-sidebar">
{/* Section 1: Sidebar Header */}
<div className="app-sidebar-header">
<h1>Notes</h1>
<button onClick={onAddNote}>Add</button> {/*onDeleteNote() use*/}
</div>
{/* Section 2: Sidebar Notes */}
<div className="app-sidebar-notes">
{sortedNotes.map((note)=> // Map Function for Dynamically Use Sorted Notes
<div className={`app-sidebar-note ${note.id === activeNote && "active" /* Dynamically return "active" if condition true note: using template literal for js logic*/}`} onClick={()=>setActiveNote(note.id)}> {/* setActiveNote() to show current Note Active*/}
{/* Section 2.1: Sidebar Notes Title */}
<div className="sidebar-note-title">
<strong>{note.title}</strong> {/*using props.note title*/}
<button onClick={()=>onDeleteNote(note.id)}>Delete</button> {/*onDeleteNote() use, Note: onDelete()❌ -> ()=>onDelete() ✔️ ⭐*/}
</div>
{/* Section 2.2: Sidebar Notes Preview */}
<p>{note.body && (note.body.length>100? note.body.substr(0,100)+"..." : note.body)}</p> {/* Using props.note body and only show upto 100 string if greater ⭐*/}
<small className="note-meta">{new Date(note.lastModified).toLocaleDateString("en-GB", { hour: "2-digit", minute:"2-digit"})}</small> {/* show note.lastModified to date format and also minute and Hour ⭐*/}
</div>
)}
</div>
</div>
);
}
export default Sidebar;//App.js
import React from "react";
import Sidebar from "./components/Sidebar";
import Main from "./components/Main";
import { useState , useEffect} from "react";
import uuid from "react-uuid"; // library for random ID ⭐
function App() {
// Notes State Array
const [notes, setNotes] = useState(JSON.parse(localStorage.notes) || []); // Initalize Notes to Local Storage JSON if not defined than default⭐
const [activeNote, setActiveNote] = useState(false); // State (initialy boolean=false) for Active or not ⭐
// Save Notes to local storage i.e. browser's internal storage
useEffect(()=>{
localStorage.setItem("notes", JSON.stringify(notes));
}, [notes]);
// Note: All Event handling function will be Arrow function.
const onAddNote = () => {
const newNote = {
id: uuid(), // random 'uuid' for the object ⭐
title: "Untitled Note",
body: "",
lastModified: Date.now(), // Javascript function for current date ⭐
};
setNotes([newNote, ...notes]); // Append an object in State Array using spreadoperator⭐
};
const onDeleteNote = (idToDelete) => {
setNotes(notes.filter((note) => note.id !== idToDelete)); // Filter Array and Save to set States ⭐
};
// Get Active Helper function to get the current stored id
const getActiveNote = () => {
return notes.find((note)=> note.id===activeNote); // activeNote is set by seActive Note inside Sidebar.js ⭐
}
// Update Note
const onUpdateNote = (updatedNote) => {
const updatedNotesArray = notes.map((note)=>{ // Map function to update Notes array ⭐
if(note.id === activeNote){
return updatedNote; // update only for activeNote in newarray ⭐
}
return note;
});
setNotes(updatedNotesArray) // set State to Updated returned Note Array ⭐
}
return (
<div className="App">
{/* Sidebar Component*/}
<Sidebar
notes={notes} //Notes State Prop⭐
onAddNote={onAddNote}
onDeleteNote={onDeleteNote}
activeNote={activeNote} // activeNote State Prop⭐
setActiveNote={setActiveNote} // activeNotes set State Prop⭐
/>
{/* Main Component*/}
<Main
// Why not Directly passing activeNote ⭐❓
activeNote={getActiveNote()} // getActiveNote() automatically running a function as a prop and update activeNote ⭐
onUpdateNote={onUpdateNote} // Run only when called
/>
</div>
);
}
export default App;src/index.css
/*index.css*/
@import url("https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css");
* {
box-sizing: border-box;
}
/* GLOBAL STYLES */
body,
.App {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
width: 100%;
height: 100vh;
overflow: hidden;
font-size: 16px;
background: url(https://www.toptal.com/designers/subtlepatterns/patterns/lightpaperfibers.png);
}
button {
background: none;
border: 0;
margin: 0;
padding: 0;
font-size: inherit;
font-family: inherit;
cursor: pointer;
color: #08c;
}
button:hover {
color: #04c;
}
.App {
display: flex;
}
/* SIDEBAR STYLES */
.app-sidebar {
width: 30%;
height: 100vh;
border-right: 1px solid #ddd;
}
.app-sidebar-header {
display: flex;
justify-content: space-between;
padding: 25px;
}
.app-sidebar-header h1 {
margin: 0;
}
.app-sidebar-notes {
height: calc(100vh - 78px);
overflow-y: scroll;
}
.app-sidebar-note {
padding: 25px;
cursor: pointer;
}
.sidebar-note-title {
display: flex;
justify-content: space-between;
}
.app-sidebar-note button {
color: crimson;
}
.app-sidebar-note p {
margin: 10px 0;
}
.app-sidebar-note small {
display: block;
color: #999;
}
.app-sidebar-note:hover {
background: #ddd;
}
.app-sidebar-note.active,
.app-sidebar-note.active small {
background: #08c;
color: white;
}
/* NOTE EDITOR/PREVIEW (MAIN) STYLES */
.app-main {
width: 70%;
height: 100vh;
}
.app-main-note-edit,
.app-main-note-preview {
height: 50vh;
}
.no-active-note {
width: 70%;
height: 100vh;
line-height: 100vh;
text-align: center;
font-size: 2rem;
color: #999;
}
/* Editing */
.app-main-note-edit {
padding: 25px;
}
.app-main-note-edit input,
textarea {
display: block;
border: 1px solid #ddd;
margin-bottom: 20px;
width: 100%;
height: calc(50vh - 130px);
padding: 10px;
resize: none;
font-size: inherit;
font-family: inherit;
}
.app-main-note-edit input {
height: 50px;
font-size: 2rem;
}
/* Preview */
.app-main-note-preview {
border-top: 1px solid #ddd;
overflow-y: scroll;
background: rgba(0, 0, 0, 0.02);
}
.preview-title {
padding: 25px 25px 0 25px;
margin: 0;
}
.markdown-preview {
padding: 0 25px 25px 25px;
font-size: 1rem;
line-height: 2rem;
}