20
loading...
This website collects cookies to deliver better user experience
cd frontend
index.html
file. You can delete all other files here. Don't forget to go inside the index.html file to delete the links to the manifest.json and logos
. You can keep the react favicon or change it to a favicon of your choice. You can customize yours here.src
folder except the index.js
file. Then create two new folders components
and css
in the src
folder. Inside the components folder, create the following files. App.jsx
Notes.jsx
and List.jsx
and inside the css folder create the index.css
file.webvitals
import and the webvitals function at the end of the file as we won't be making use of them. Since we have changed the location of the App.jsx component we need to change the path
of the App import to thisimport App from './components/App'
import './css/index.css'
index.js
file should look like 👇import React from 'react'
import ReactDOM from 'react-dom'
import './css/index.css'
import App from './components/App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
axios
. npm install axios
"private": true,
line so it ends up like 👇."name": "frontend",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8000",
relative paths
when you are making the API requests. Instead of making use of http://localhost:8000/notes/
you can simply make use of /notes/
. Seems like a great idea right?. You'll see it in action shortly. Now let's work on the component files.function List(){
return (
<div className="note">
</div>
)
}
export default List
useState
and useEffect
. You can read more about react hooks here. We also need to import axios
and the List component we created above.import {useState, useEffect} from "react"
import axios from "axios"
import List from "./List"
state variable
as notes with an initial state of null
. state variable
as formNote with empty strings as its initial state.function Note() {
const [notes , setNewNotes] = useState(null)
const [formNote, setFormNote] = useState({
title: "",
content: ""
})
}
getNotes
function executes right after the render has been displayed on the screen.useEffect(() => {
getNotes()
} ,[])
function getNotes() {
axios({
method: "GET",
url:"/notes/",
}).then((response)=>{
const data = response.data
setNewNotes(data)
}).catch((error) => {
if (error.response) {
console.log(error.response);
console.log(error.response.status);
console.log(error.response.headers);
}
})}
GET
and then passing the relative path /notes/
as the URL. If we had not added the proxy "http://localhost:8000"
to the package.json file. We would need to declare the URL here as "http://localhost:8000/notes/"
. I believe the method we used makes the code cleaner.GET
request is made with axios, the data in the received response is assigned to the setNewNotes
function, and this updates the state variable notes
with a new state. Thus the value of the state variable changes from null
to the data in the received response
.function createNote(event) {
axios({
method: "POST",
url:"/notes/",
data:{
title: formNote.title,
content: formNote.content
}
})
.then((response) => {
getNotes()
})
setFormNote(({
title: "",
content: ""}))
event.preventDefault()
}
POST
and then passing the relative path /notes/
as the URL. We also have an additional field here data
. This will contain the data which we'll send to the backend for processing and storage in the database. That is the data from the title and content inputs in the form.POST
request is made with Axios, we don't process the response (remember that this was mentioned in part 2 when we were setting up the POST API function); we just use the response function to recall the getNotes
function so that the previous notes can be displayed together with the newly added note.setFormNote
function. Then we also have to ensure that the form submission does not make the page reload so we add the event.preventDefault
function which prevents the default action of the form submission.function DeleteNote(id) {
axios({
method: "DELETE",
url:`/notes/${id}/`,
})
.then((response) => {
getNotes()
});
}
id
parameter so that we can pass the id of the particular note which we want to delete as an argument later on.DELETE
request is made with Axios, we don't process the response as well; we just use the response function to call the getNotes
function so that the notes get method can get executed once again and we'll now see the remaining notes retrieved from the database.function handleChange(event) {
const {value, name} = event.target
setFormNote(prevNote => ({
...prevNote, [name]: value})
)}
Note
function.return (
<div className=''>
<form className="create-note">
<input onChange={handleChange} text={formNote.title} name="title" placeholder="Title" value={formNote.title} />
<textarea onChange={handleChange} name="content" placeholder="Take a note..." value={formNote.content} />
<button onClick={createNote}>Create Post</button>
</form>
{ notes && notes.map(note => <List
key={note.id}
id={note.id}
title={note.title}
content={note.content}
deletion ={DeleteNote}
/>
)}
</div>
);
List
component, we need to first confirm that at least one single note was retrieved from the database so that we don't pass null data to the List
component.List
component.Note
component so it can be used in the App.jsx
file.export default Note;
import {useState, useEffect} from "react";
import axios from "axios";
import List from "./List"
function Note() {
const [notes , setNewNotes] = useState(null)
const [formNote, setFormNote] = useState({
title: "",
content: ""
})
useEffect(() => {
getNotes()
} ,[])
function getNotes() {
axios({
method: "GET",
url:"/notes/",
}).then((response)=>{
const data = response.data
setNewNotes(data)
}).catch((error) => {
if (error.response) {
console.log(error.response);
console.log(error.response.status);
console.log(error.response.headers);
}
})}
function createNote(event) {
axios({
method: "POST",
url:"/notes/",
data:{
title: formNote.title,
content: formNote.content
}
})
.then((response) => {
getNotes()
})
setFormNote(({
title: "",
content: ""}))
event.preventDefault()
}
function DeleteNote(id) {
axios({
method: "DELETE",
url:`/notes/${id}/`,
})
.then((response) => {
getNotes()
})
}
function handleChange(event) {
const {value, name} = event.target
setFormNote(prevNote => ({
...prevNote, [name]: value})
)}
return (
<div className=''>
<form className="create-note">
<input onChange={handleChange} text={formNote.title} name="title" placeholder="Title" value={formNote.title} />
<textarea onChange={handleChange} name="content" placeholder="Take a note..." value={formNote.content} />
<button onClick={createNote}>Create Post</button>
</form>
{ notes && notes.map(note => <List
key={note.id}
id={note.id}
title={note.title}
content={note.content}
deletion ={DeleteNote}
/>
)}
</div>
);
}
export default Note;
List.jsx
file to finish creating the List
component.function List(props){
function handleClick(){
props.deletion(props.id)
}
return (
<div className="note">
<h1 > Title: {props.title} </h1>
<p > Content: {props.content}</p>
<button onClick={handleClick}>Delete</button>
</div>
)
}
export default List;
props
; which gives us access to the title, content and id of the note. We pass the id to an onClick function which in turn calls the delete function in the Note function with id
as the argument.Note
function into the App.jsx
file.import Note from "./Notes"
function App() {
return (
<div className='App'>
<Note />
</div>
);
}
export default App;
npm run build
manage.py
filecd ..
python manage.py runserver
cd frontend
+
icon. Run:npm install @material-ui/icons
AddIcon
from the installed material ui icon package into the Notes
componentimport AddIcon from "@material-ui/icons/Add";
useState
hooks once again to achieve this.const [isExpanded, setExpanded]= useState(false)
const [rows, setRows]= useState(1)
state variable
as isExpanded with an initial state of false
so the text input and add button are hidden when the page is loaded.state variable
as rows with an initial state of 1
function NoteShow(){
setExpanded(true)
setRows(3)
}
Noteshow
which gets called when the text area input is clicked. <form className="create-note">
{isExpanded && <input onChange={handleChange} text={formNote.title} name="title" placeholder="Title" value={formNote.title} />}
<textarea onClick={NoteShow} onChange={handleChange} name="content" placeholder="Take a note..." rows={rows} value={formNote.content} />
{isExpanded && <button onClick={createNote}>
<AddIcon />
</button>}
</form>
isExpanded
condition is added to the text input and button as explained earlier. When the textarea input is clicked, the NoteShow
function is called and two things happen.setExpanded
function is called with the argument true
which changes the state to true and then the hidden components are displayed setRows
function is called with the argument 3
setExpanded(false)
to the end of the createNote functionfunction createNote(event) {
axios({
method: "POST",
url:"/notes/",
data:{
title: formNote.title,
content: formNote.content
}
})
.then((response) => {
getNotes()
})
setFormNote(({
title: "",
content: ""}))
setExpanded(false)
event.preventDefault()
}
import {useState, useEffect} from "react";
import axios from "axios";
import List from "./List"
import AddIcon from "@material-ui/icons/Add";
function Note() {
const [isExpanded, setExpanded]= useState(false)
const [rows, setRows]= useState(1)
const [notes , setNewNotes] = useState(null)
const [formNote, setFormNote] = useState({
title: "",
content: ""
})
useEffect(() => {
getNotes()
} ,[])
function getNotes() {
axios({
method: "GET",
url:"/notes/",
}).then((response)=>{
const data = response.data
setNewNotes(data)
}).catch((error) => {
if (error.response) {
console.log(error.response);
console.log(error.response.status);
console.log(error.response.headers);
}
})}
function createNote(event) {
axios({
method: "POST",
url:"/notes/",
data:{
title: formNote.title,
content: formNote.content
}
})
.then((response) => {
getNotes()
})
setFormNote(({
title: "",
content: ""}))
setExpanded(false)
event.preventDefault()
}
function DeleteNote(id) {
axios({
method: "DELETE",
url:`/notes/${id}/`,
})
.then((response) => {
getNotes()
})
}
function handleChange(event) {
const {value, name} = event.target
setFormNote(prevNote => ({
...prevNote, [name]: value})
)}
function NoteShow(){
setExpanded(true)
setRows(3)
}
return (
<div className=''>
<form className="create-note">
{isExpanded && <input onChange={handleChange} text={formNote.title} name="title" placeholder="Title" value={formNote.title} />}
<textarea onClick={NoteShow} onChange={handleChange} name="content" placeholder="Take a note..." rows={rows} value={formNote.content} />
{isExpanded && <button onClick={createNote}>
<AddIcon />
</button>}
</form>
{ notes && notes.map(note => <List
key={note.id}
id={note.id}
title={note.title}
content={note.content}
deletion ={DeleteNote}
/>
)}
</div>
);
}
export default Note;
Header.jsx
in the components folder. This will hold our header elements.function Header() {
return (
<header>
<h1>Notes</h1>
</header>
);
}
export default Header;
Footer.jsx
in the components folder.This will hold our footer elements.function Footer() {
const year = new Date().getFullYear();
return (
<footer>
<p>Copyright ⓒ {year}</p>
</footer>
);
}
export default Footer;
Date().getFullYear()
method to get the year of the current date and pass it to the p
element in our footer.App.jsx
file and then call them.import Note from "./Notes"
import Header from "./Header"
import Footer from "./Footer"
function App() {
return (
<div className='App'>
<Header />
<Note />
<Footer />
</div>
);
}
export default App;
classNames
have already been included while we were building the application. CREATE
,READ
and DELETE
functionalities. You can explore and have fun with your application now. npm run build
manage.py
filecd ..
python manage.py runserver