part2b
Forms
add notes state
import { useState } from "react";import Note from "./components/Note";
const App = (props) => { const [notes, setNotes] = useState(props.notes);
return ( <div> <h1>Notes</h1> <ul> {notes.map((note) => ( <Note key={note.id} note={note} /> ))} </ul> </div> );};
export default App;
add form and event handler
const App = (props) => { const [notes, setNotes] = useState(props.notes);
const addNote = (event) => { event.preventDefault(); console.log("button clicked", event.target); };
return ( <div> <h1>Notes</h1> <ul> {notes.map((note) => ( <Note key={note.id} note={note} /> ))} </ul>
<form onSubmit={addNote}> <input /> <button type="submit">save</button> </form> </div> );};
Controlled component (input)
Deal with form input, notice about the onChange warning.
const App = (props) => { const [notes, setNotes] = useState(props.notes);
const [newNote, setNewNote] = useState("a new note...");
const addNote = (event) => { event.preventDefault(); console.log("button clicked", event.target); };
const handleNoteChange = (event) => { console.log(event.target.value); setNewNote(event.target.value); };
return ( <div> <h1>Notes</h1> <ul> {notes.map((note) => ( <Note key={note.id} note={note} /> ))} </ul> <form onSubmit={addNote}> <input value={newNote} onChange={handleNoteChange} /> <button type="submit">save</button> </form> </div> );};
Complete addNote function
const addNote = (event) => { event.preventDefault(); const noteObject = { content: newNote, date: new Date().toISOString(), important: Math.random() < 0.5, id: notes.length + 1, };
setNotes(notes.concat(noteObject)); setNewNote("");};
Filtering Displayed Elements
const App = (props) => { const [notes, setNotes] = useState(props.notes); const [newNote, setNewNote] = useState("");
const [showAll, setShowAll] = useState(true);
// ...};
Change render notes to notesToShow
import { useState } from "react";import Note from "./components/Note";
const App = (props) => { const [notes, setNotes] = useState(props.notes); const [newNote, setNewNote] = useState(""); const [showAll, setShowAll] = useState(true);
// ...
const notesToShow = showAll ? notes : notes.filter((note) => note.important === true);
return ( <div> <h1>Notes</h1> <ul> {notesToShow.map((note) => ( <Note key={note.id} note={note} /> ))} </ul> // ... </div> );};
Add the filter button
import { useState } from "react";import Note from "./components/Note";
const App = (props) => { const [notes, setNotes] = useState(props.notes); const [newNote, setNewNote] = useState(""); const [showAll, setShowAll] = useState(true);
// ...
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} /> ))} </ul> // ... </div> );};
Exercises
2.6 The phonebook step 1
A phonebook that can add name and show the names.
Start point
import { useState } from "react";
const App = () => { const [persons, setPersons] = useState([{ name: "Arto Hellas" }]); const [newName, setNewName] = useState("");
return ( <div> <h2>Phonebook</h2> <form> <div> name: <input /> </div> <div> <button type="submit">add</button> </div> </form> <h2>Numbers</h2> ... </div> );};
export default App;
2.7 The phonebook step 2
Prevent the use from being able to add names that already exist in the phonebook. Alert when such an action is attempted.
ref: lodash isEqual
2.8 The phonebook step 3
Allow users to add phone numbers.
2.9* The phonebook step 4
Implement a search field to filter results.
const App = () => { const [persons, setPersons] = useState([ { name: "Arto Hellas", number: "040-123456", id: 1 }, { name: "Ada Lovelace", number: "39-44-5323523", id: 2 }, { name: "Dan Abramov", number: "12-43-234345", id: 3 }, { name: "Mary Poppendieck", number: "39-23-6423122", id: 4 }, ]);
// ...};
2.10 The phonebook step 5
Extract three components: Filter, PersonForm, Persons(and Person)
ref:
const App = () => { // ...
return ( <div> <h2>Phonebook</h2>
<Filter ... />
<h3>Add a new</h3>
<PersonForm ... />
<h3>Numbers</h3>
<Persons ... /> </div> )}
My solutions
My solutions:
(Please complete your own solutions before click here.)
2.6 Phonebook that can add name
import { useState } from "react";
const App = () => { const [persons, setPersons] = useState([{ name: "Arto Hellas" }]); const [newName, setNewName] = useState("");
const handleNameChange = (event) => { event.preventDefault(); setNewName(event.target.value); };
const addPerson = (event) => { event.preventDefault(); setPersons( persons.concat({ name: newName, }) ); setNewName(""); };
return ( <div> <h2>Phonebook</h2> <form onSubmit={addPerson}> <div> name: <input value={newName} onChange={handleNameChange} /> </div> <div> <button type="submit">add</button> </div> </form> <h2>Numbers</h2> {persons.map((person) => ( <p key={person.name}>{person.name}</p> ))} </div> );};
export default App;
2.7 no same person name
To use lodash _.isEqual
pnpm i lodash
const addPerson = (event) => { event.preventDefault(); // console.log(persons) // console.log({ name: newName }) // console.log(_.includes(persons, { name: newName })) // console.log(persons.filter(person => _.isEqual(person, { name: newName })).length > 0) if ( persons.filter((person) => _.isEqual(person, { name: newName })).length > 0 ) { alert(`${newName} is already added to phonebook`); return; } setPersons( persons.concat({ name: newName, }) ); setNewName("");};
2.8 second input form to add number
import { useState } from "react";var _ = require("lodash");
const App = () => { const [persons, setPersons] = useState([ { name: "Arto Hellas", number: "040-1234567" }, ]); const [newName, setNewName] = useState(""); const [newNumber, setNewNumber] = useState("");
const handleNameChange = (event) => { event.preventDefault(); setNewName(event.target.value); }; const handleNumberChange = (event) => { event.preventDefault(); setNewNumber(event.target.value); };
const addPerson = (event) => { event.preventDefault(); // console.log(persons) // console.log({ name: newName }) // console.log(_.includes(persons, { name: newName })) // console.log(persons.filter(person => _.isEqual(person, { name: newName })).length > 0)
// console.log(persons) // console.log(persons.filter(person => _.includes(person, newName)).length > 0) if (persons.filter((person) => _.includes(person, newName)).length > 0) { alert(`${newName} is already added to phonebook`); return; } setPersons( persons.concat({ name: newName, number: newNumber, }) ); setNewName(""); setNewNumber(""); };
return ( <div> <h2>Phonebook</h2> <form onSubmit={addPerson}> <div> name: <input value={newName} onChange={handleNameChange} /> <br /> number: <input value={newNumber} onChange={handleNumberChange} /> </div> <div> <button type="submit">add</button> </div> </form> <h2>Numbers</h2> {persons.map((person) => ( <p key={person.name}> {person.name} {person.number} </p> ))} </div> );};
export default App;
2.9 filter
import { useState } from "react";var _ = require("lodash");
const App = () => { const [persons, setPersons] = useState([ { name: "Arto Hellas", number: "040-123456", id: 1 }, { name: "Ada Lovelace", number: "39-44-5323523", id: 2 }, { name: "Dan Abramov", number: "12-43-234345", id: 3 }, { name: "Mary Poppendieck", number: "39-23-6423122", id: 4 }, ]); const [newName, setNewName] = useState(""); const [newNumber, setNewNumber] = useState(""); const [filterName, setFilterName] = useState("");
const handleNameChange = (event) => { event.preventDefault(); setNewName(event.target.value); }; const handleNumberChange = (event) => { event.preventDefault(); setNewNumber(event.target.value); };
const addPerson = (event) => { event.preventDefault(); // console.log(persons) // console.log({ name: newName }) // console.log(_.includes(persons, { name: newName })) // console.log(persons.filter(person => _.isEqual(person, { name: newName })).length > 0)
// console.log(persons) // console.log(persons.filter(person => _.includes(person, newName)).length > 0) if (persons.filter((person) => _.includes(person, newName)).length > 0) { alert(`${newName} is already added to phonebook`); return; } setPersons( persons.concat({ name: newName, number: newNumber, id: persons.length + 1, }) ); 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> <form> <div> filter shown with: <input value={filterName} onChange={handleFilterName} /> </div> </form> <h2>add a new</h2> <form onSubmit={addPerson}> <div> name: <input value={newName} onChange={handleNameChange} /> <br /> number: <input value={newNumber} onChange={handleNumberChange} /> </div> <div> <button type="submit">add</button> </div> </form> <h2>Numbers</h2> {personsToShow.map((person) => ( <p key={person.name}> {person.name} {person.number} </p> ))} </div> );};
export default App;
2.10 component modules
import { useState } from 'react'import Filter from './components/Filter'import PersonForm from './components/PersonForm'import Persons from './components/Persons'var _ = require('lodash')
const App = () => { const [persons, setPersons] = useState([ { name: 'Arto Hellas', number: '040-123456', id: 1 }, { name: 'Ada Lovelace', number: '39-44-5323523', id: 2 }, { name: 'Dan Abramov', number: '12-43-234345', id: 3 }, { name: 'Mary Poppendieck', number: '39-23-6423122', id: 4 } ]) const [newName, setNewName] = useState('') const [newNumber, setNewNumber] = useState('') const [filterName, setFilterName] = useState('')
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 } setPersons(persons.concat({ name: newName, number: newNumber, id: persons.length + 1 })) 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
import React from 'react'
const Filter = (props) => {
return ( <form> <div> filter shown with: <input value={props.filterName} onChange={props.onChange} /> </div> </form> )}
export default Filter
import React from 'react'
const PersonForm = (props) => {
return ( <form onSubmit={props.addPerson}> <div> name: <input value={props.newName} onChange={props.handleNameChange} /> <br /> number: <input value={props.newNumber} onChange={props.handleNumberChange} /> </div> <div> <button type="submit">add</button> </div> </form> )}export default PersonForm
import React from 'react'
const Person = ({ person }) => { return ( <p key={person.name}>{person.name} {person.number}</p> )}
const Persons = (props) => { const { personsToShow } = props
return ( <> { personsToShow.map( person => <Person key={person.id} person={person} /> ) } </> )}
export default Persons