part2d
Altering data in server
Sending data to the server
const addNote = (event) => { event.preventDefault(); const noteObject = { content: newNote, data: new Date().toISOString(), important: Math.random() < 0.5, id: String(notes.length + 1), };
axios.post("http://localhost:3001/notes", noteObject).then((response) => { setNotes(notes.concat(noteObject)); setNewNote(""); });};
Changing the importance of notes
const App = () => { const [notes, setNotes] = useState([]) const [newNote, setNewNote] = useState('') const [showAll, setShowAll] = useState(true)
// ...
const toggleImportanceOf = id => {const url = `http://localhost:3001/notes/${id}`const note = notes.find(n => n.id === id)const changedNote = { ...note, important: !note.important }
axios.put(url, changedNote).then(response => { setNotes(notes.map(n => n.id !== id ? n : response.data)) })
}
// ...
return (<div><h1>Notes</h1><div><button onClick={() => setShowAll(!showAll)}>show {showAll ? 'important' : 'all' }</button></div> <ul>{notesToShow.map(note =><Notekey={note.id}note={note}
toggleImportance={() => toggleImportanceOf(note.id)} /> )} </ul> // ... </div>
)}
const Note = ({ note, toggleImportance }) => { const label = note.important ? 'make not important' : 'make important'
return ( <li> {note.content} <button onClick={toggleImportance}>{label}</button> </li> )}
Extracting Communication with the Backend into a Separate Module
Add folder src/services and add new file notes.js
import axios from "axios";const baseUrl = "http://localhost:3001/notes";
const getAll = () => { const request = axios.get(baseUrl); return request.then((response) => response.data);};
const create = (newObject) => { const request = axios.post(baseUrl, newObject); return request.then((response) => response.data);};
const update = (id, newObject) => { const request = axios.put(`${baseUrl}/${id}`, newObject); return request.then((response) => response.data);};
export default { getAll, create, update };
import in App.js and use the noteService
import { useState, useEffect } from "react";import Note from "./components/Note";import noteService from "./services/notes";
const App = () => { const [notes, setNotes] = useState([]); const [newNote, setNewNote] = useState(""); const [showAll, setShowAll] = useState(true);
useEffect(() => { noteService.getAll().then((initialNotes) => { setNotes(initialNotes); }); }, []); console.log("render", notes.length, "notes");
const addNote = (event) => { event.preventDefault(); const noteObject = { content: newNote, data: new Date().toISOString(), important: Math.random() < 0.5, id: String(notes.length + 1), };
noteService.create(noteObject).then((returnedNote) => { setNotes(notes.concat(returnedNote)); setNewNote(""); }); };
const handleNoteChange = (event) => { console.log(event.target.value); setNewNote(event.target.value); };
const notesToShow = showAll ? notes : notes.filter((note) => note.important);
const toggleImportanceOf = (id) => { const note = notes.find((n) => n.id === id); const changedNote = { ...note, important: !note.important };
noteService.update(id, changedNote).then((returnedNote) => { setNotes(notes.map((note) => (note.id !== id ? note : returnedNote))); }); };
return ( <div> <h1>Notes</h1> <div> <button onClick={() => setShowAll(!showAll)}> show {showAll ? "important" : "all"} </button> </div> <ul> {notesToShow.map((note) => ( <Note key={note.id} note={note} toggleImportance={() => toggleImportanceOf(note.id)} /> ))} </ul> <form onSubmit={addNote}> <input value={newNote} onChange={handleNoteChange} /> <button type="submit">save</button> </form> </div> );};
export default App;
Below two lines have been actioned.
return response data directly
cleaner syntax for defining object literals
Promises and Errors
// ...
const toggleImportanceOf = (id) => { const note = notes.find((n) => n.id === id); const changedNote = { ...note, important: !note.important };
noteService .update(id, changedNote) .then((returnedNote) => { setNotes(notes.map((note) => (note.id !== id ? note : returnedNote))); })
.catch((error) => { alert(`the note '${note.content}' was already deleted from server`); setNotes(notes.filter((n) => n.id !== id)); });};
// ...
Full stack developer’s oath
Full stack development is extremely hard, that is why I will use all the possible means to make it easier
- I will have my browser developer console open all the time
-
I will use the network tab of the browser dev tools to ensure that frontend and backend are communicating as I expect
-
I will constantly keep an eye on the state of the server to make sure that the data sent there by the frontend is saved there as I expect
- I will progress with small steps
- I will write lots of console.log statements to make sure I understand how the code behaves and to help pinpoint problems
- If my code does not work, I will not write more code. Instead, I start deleting the code until it works or just return to a state when everything was still working
- When I ask for help in the course Discord or Telegram channel or elsewhere I formulate my questions properly, see here how to ask for help
Exercises
2.12 The phonebook step 7
Add number saves to the backend server.
2.13 The phonebook step 8
Extract the code that handles the communication with the backend into its own module.
2.14 The phonebook step 9
Delete button with a window.confirm method confirming.
2.15* The phonebook step 10
Change the functionality so that if a number is added to an already existing user, the new number will replace the old number. It’s recommended to use the HTTP PUT method for updating the phone number.
If the person’s information is already in the phonebook, the application can ask the user to confirm the action.
My solutions
My solutions:
(Please complete your own solutions before click here.)
2.12 The phonebook step 7
Add number saves to the backend server.
2.13 The phonebook step 8
Extract the code that handles the communication with the backend into its own module.
import axios from 'axios'const baseUrl = 'http://localhost:3001/persons'
const getAll = () => {const request = axios.get(baseUrl)return request.then(response => response.data)}
const create = newObject => {const request = axios.post(baseUrl, newObject)return request.then(response => response.data)}
const update = (id, newObject) => {const request = axios.put(`${baseUrl}/${id}`, newObject)return request.then(response => response.data)}
export default { getAll, create, update }
import { useState, useEffect } from 'react'import personService from './services/persons'import Filter from './components/Filter'import PersonForm from './components/PersonForm'import Persons from './components/Persons'var _ = require('lodash')
const App = () => {const [persons, setPersons] = useState([])const [newName, setNewName] = useState('')const [newNumber, setNewNumber] = useState('')const [filterName, setFilterName] = useState('')
useEffect(() => {personService.getAll().then(initialPersons => {setPersons(initialPersons)})}, [])console.log('render', persons.length, 'persons')
const handleNameChange = (event) => {event.preventDefault()setNewName(event.target.value)}const handleNumberChange = (event) => {event.preventDefault()setNewNumber(event.target.value)}
const addPerson = (event) => {event.preventDefault()
if (persons.filter(person => _.includes(person, newName)).length > 0) { alert(`${newName} is already added to phonebook`) return }
const personObject = { name: newName, number: newNumber, id: String(persons.length + 1) }
personService .create(personObject) .then(returnedPerson => { setPersons(persons.concat(returnedPerson)) setNewName('') setNewNumber('') })
}
const handleFilterName = (event) => {event.preventDefault()setFilterName(event.target.value)}
const personsToShow = persons.filter(person => \_.includes(person.name.toLowerCase(), filterName.toLowerCase()))
return (<div><h2>Phonebook</h2><Filter filterName={filterName} onChange={handleFilterName} /><h2>Add a new</h2><PersonForm addPerson={addPerson} newName={newName} newNumber={newNumber} handleNameChange={handleNameChange} handleNumberChange={handleNumberChange} /><h2>Numbers</h2><Persons personsToShow={personsToShow} /></div>)}
export default App
2.14 The phonebook step 9
Delete button with a window.confirm method confirming.
import React from 'react'
const Person = ({ person, handleClick }) => {return (<p key={person.name}>{person.name} {person.number}<button onClick={() => handleClick(person)}>delete</button></p>)}
const Persons = (props) => {const { personsToShow, handleClick } = props
return (<>{personsToShow.map(person => <Person key={person.id} person={person} handleClick={handleClick} />)}</>)}
export default Persons
import axios from "axios";const baseUrl = "http://localhost:3001/persons";
const getAll = () => { const request = axios.get(baseUrl); return request.then((response) => response.data);};
const create = (newObject) => { const request = axios.post(baseUrl, newObject); return request.then((response) => response.data);};
const update = (id, newObject) => { const request = axios.put(`${baseUrl}/${id}`, newObject); return request.then((response) => response.data);};
const deleteId = (id) => { const request = axios.delete(`${baseUrl}/${id}`); return request.then((response) => { // console.log(response.data) return response.data; });};
export default { getAll, create, update, deleteId };
import { useState, useEffect } from "react";import personService from "./services/persons";import Filter from "./components/Filter";import PersonForm from "./components/PersonForm";import Persons from "./components/Persons";var _ = require("lodash");
const App = () => { const [persons, setPersons] = useState([]); const [newName, setNewName] = useState(""); const [newNumber, setNewNumber] = useState(""); const [filterName, setFilterName] = useState("");
useEffect(() => { personService.getAll().then((initialPersons) => { setPersons(initialPersons); }); }, []); // console.log('render', persons.length, 'persons')
const handleNameChange = (event) => { event.preventDefault(); setNewName(event.target.value); }; const handleNumberChange = (event) => { event.preventDefault(); setNewNumber(event.target.value); };
const addPerson = (event) => { event.preventDefault();
if (persons.filter((person) => _.includes(person, newName)).length > 0) { alert(`${newName} is already added to phonebook`); return; }
const personObject = { name: newName, number: newNumber, id: String(persons.length + 1), };
personService.create(personObject).then((returnedPerson) => { setPersons(persons.concat(returnedPerson)); setNewName(""); setNewNumber(""); }); };
const handleFilterName = (event) => { event.preventDefault(); setFilterName(event.target.value); };
const personsToShow = persons.filter((person) => _.includes(person.name.toLowerCase(), filterName.toLowerCase()) );
const handlePersonDelete = (person) => { // console.log(`deleting person ${id}`)
if (window.confirm(`delete ${person.name} ?`)) { personService.deleteId(person.id).then((responsePerson) => { setPersons(persons.filter((n) => n.id !== responsePerson.id)); }); } };
return ( <div> <h2>Phonebook</h2> <Filter filterName={filterName} onChange={handleFilterName} /> <h2>Add a new</h2> <PersonForm addPerson={addPerson} newName={newName} newNumber={newNumber} handleNameChange={handleNameChange} handleNumberChange={handleNumberChange} /> <h2>Numbers</h2> <Persons personsToShow={personsToShow} handleClick={handlePersonDelete} /> </div> );};
export default App;
2.15 The phonebook step 10
When adding person,
if exist, window.confirm, if confirmed, update the phone number
else add the person.
const addPerson = (event) => { event.preventDefault();
const personObject = { name: newName, number: newNumber, id: String(persons.length + 1), };
const existPersonArray = persons.filter((person) => _.includes(person, newName) ); // console.log(existPersonArray)
if (existPersonArray.length > 0) { // alert(`${newName} is already added to phonebook`)
const existPerson = existPersonArray[0]; // console.log(existPerson)
if ( window.confirm( `${newName} is already added to phonebook, replace the old number with a new one?` ) ) { const id = existPerson.id; personService .update(id, personObject) .then((returnedPerson) => { setPersons( persons.map((person) => person.id !== id ? person : returnedPerson ) ); setNewName(""); setNewNumber(""); }) .catch((error) => { alert( `the person '${existPerson.name}' was already deleted from server` ); setPersons(persons.filter((person) => person.id !== id)); }); } } else { personService.create(personObject).then((returnedPerson) => { setPersons(persons.concat(returnedPerson)); setNewName(""); setNewNumber(""); }); }};