Tempo di lettura: 16 minuti
Ho deciso di scrivere questa mini guida a React Hooks per mostrarti come sia possibile oggi gestire gli stati dei componenti senza utilizzare le classi e quindi il costruttore, ma semplicemente utilizzando i functional components .
Guida a React Hooks
Gli Hooks in React vengono introdotti dalla versione 16.8 in poi per risolvere una serie di problemi disconnessi tra di loro ,per saperne di più dai un’occhiata al sito ufficiale della libreria .
Grazie ad essi possiamo gestire gli stati dei componenti utilizzando semplicemente le funzioni nella realizzazione del componente e non le classi , quindi omettendo il costruttore. Comunque si è liberi di utilizzare tranquillamente entrambi i metodi di programmazione visto che l’uno non esclude l’altro, ciò dipende dai nostri gusti o dal nostro approccio nella stesura di codice.
Il “Miglior modo per imparare la Teoria è quello di farlo con la pratica” e non leggendo centinaia di righe soporifere, quindi dalla prossima sezione andremo a realizzare un’applicazione in React che chiameremo Heroes dove gestiremo un’ipotetica agenzia dedita ai super eroi.
Mi aspetto che abbiate una minima conoscenza di Html5, Css3, framework css (Bootstrap o Materialize) e quanto meno le basi di ES6/ES7 fondamentale nella realizzazione di applicazioni in React. Se volete potete , prima di iniziare , dare uno sguardo alla mia mini guida dedita a javascript ES6 , o se preferite seguire il mio corso gratuito su udemy.
Prepariamo l’occorrente per lavorare con React
Viso che andremo a lavorare da riga di comando per scaricare diversi pacchetti che ci serviranno , installiamo node con npm dal sito ufficiale. Una volta scaricati ed installati aprite il terminale e verificatene la versione:
PS E:\Es6\ESReact\note> node -v v10.15.3 PS E:\Es6\ESReact\note> npm -v 6.4.1
Il mio consiglio è quello di utilizzare un buon ide che abbia un buon auto complete e che si interfacci ottimamente con js. La scelta cade sicuramente su visual studio code che tra l’altro è open source,il che non guasta, e da la possibilità di aggiungere centinaia di estensioni le quali agevolano di molto la stesura di codice.
Basta per ora non ci occrre altro!!
Creiamo l’applicazione Heroes
Createvi ,innanzitutto, una cartella nel vostro pc per organizzare al meglio le vostre applicazioni. Una volta fatto aprite il terminale, se utilizzate Visual studio potete utilizzare il suo, e recatevi all’interno della cartella appena creata con il comando:
PS E:\Es6> cd.. PS E:\> cd .\react\test PS E:\react\test>
Una volta siti all’interno della cartella , sempre da terminale digitate:
npx create-react-app heroes
Attendete qualche istante che venga scaricato il pacchetto contenente tutto il necessario atto al funzionamento di una applicazione React (dipende dalla velocità della vostra connessione internet). Una volta finito recatevi all’interno della cartella creata:
PS E:\react\test> cd .\heroes\ PS E:\react\test\heroes>
La struttura che vi si presenta è la seguente:
Da terminale digitate :
PS E:\react\test\heroes> npm start
con questo comando avete avviato il server e dato vita all’applicazione
I componenti in React
Anche React , come ogni altra libreria o framework che si rispetti, è suddiviso in componenti i quali hanno specifici compiti più o meno importanti.
Il componente principale o radice è app.js che si trova all’interno della cartella src. Apritelo e cancellate il contenuto che si trova all’interno del tag div:
cancellate anche l’import relativo al logo che non ci interessa.
Digitate:
import React from 'react'; import './App.css'; const heroes = [ { id: 1, name: 'Hulk', date: '2019-05-30T17:30:31.098Z', important: true }, { id: 2, name: 'Spiderman', date: '2019-05-30T18:39:34.091Z', important: false }, { id: 3, name: 'Superman', date: '2019-05-30T19:20:14.298Z', important: true } ] const App = () => { return ( <div> <h1>Heroes</h1> <ul> <li>{heroes[0].name}</li> <li>{heroes[1].name}</li> <li>{heroes[2].name}</li> </ul> </div> ); } export default App;
app.js
Ora se avviate il server e quindi aprite il browser vedrete una lista di eroi.
Come vedete ho creato una lista non ordinata dove accedo ad ogni eroe dell’array tramite chiave valore. Tutto ciò si può semplificare utilizzando la funzione map introdotta da ES6 la quale mi scorre l’array e restituisce ogni singolo valore nella lista:
... <ul> {heroes.map(heroe => <li>{heroe.name}</li>)} </ul> ...
Key-attribute
Se aprite la console del browser noterete un errore:
ciò vuol dire che React , quando si realizzano liste di valori ,esige che questi abbiano una chiave univoca in modo tale da poter essere identificate univocamente,per cui aggiungete:
{heroes.map(heroe => <li key={heroe.id}>{heroe.name}</li>)}
come notate ho aggiunto una chiave key al tag li con valore dato dall’id dell’eroe il quale è univoco. Questo ha fatto sparire l’errore.
La destrutturazione in React
Come detto in precedenza un’applicazione React è costituita da più componenti i quali devono avere compiti specifici più o meno importanti.
Per cui deleghiamo la visualizzazione della lista degli erori al componente Hero, quindi all’interno della cartella src realiziamo la cartella components ed all’interno di questa il file Hero.js:
import React from 'react'; const Hero = () => { return ( <div> </div> ); } export default Hero;
Hero.js
Ora importatelo all’interno del componente App.js:
import React from 'react'; import './App.css'; import Hero from './components/Hero.js' ...
app.js
Sempre nel file app.js miglioriamo la formattazione per la funzione di supporto che restituisce le righe di eroi nella nostra applicazione realizzando una costante di supporto subito prima di return:
const rows = () => heroes.map(hero => <li key={hero.id}> {hero.name} </li> ) ...
quindi il file intero diventa:
import React from 'react'; import './App.css'; import Hero from './components/Hero.js' const heroes = [ { id: 1, name: 'Hulk', date: '2019-05-30T17:30:31.098Z', important: true }, { id: 2, name: 'Spiderman', date: '2019-05-30T18:39:34.091Z', important: false }, { id: 4, name: 'Superman', date: '2019-05-30T19:20:14.298Z', important: true } ] const App = () => { const rows = () => heroes.map(hero => <li key={hero.id}> {hero.name} </li> ) return ( <div> <h1>Heroes</h1> <ul> {rows()} </ul> </div> ); } export default App;
Il componente che si occuperà di restituire la lista di eroi sarà Hero.js, per cui apritelo e scrivete:
import React from 'react' const Hero = ({ hero }) => { return ( <li>{hero.name}</li> ) } export default Hero
quindi modificate anche il file App.js:
... const App = () => { const rows = () => heroes.map(hero => <Hero key={hero.id} hero={hero} > </Hero> ) return ( <div> <h1>Heroes</h1> <ul> {rows()} </ul> </div> ); } export default App;
Come potete notare utilizzo il componente hero appena importato come se fosse un tag.
I forms e React Hooks
In questo paragrafo daremo la possibilità agli utenti di aggiungere nuovi eroi , quindi introdurremo i forms gestendone lo stato grazie agli Hooks in React.
Aprite il file App.js ed importate la funzione useState introdotta in React 16.8.0, questa servirà a gestire lo stato del componente in quanto la gestione del form avviene tramite esso:
import React, {useState} from 'react'; import './App.css'; import Hero from './components/Hero.js' const App = () => { const heroes = [ { id: 1, name: 'Hulk', date: '2019-05-30T17:30:31.098Z', important: true }, { id: 2, name: 'Spiderman', date: '2019-05-30T18:39:34.091Z', important: false }, { id: 4, name: 'Superman', date: '2019-05-30T19:20:14.298Z', important: true } ] const [Eroi, setEroi] = useState(heroes) const rows = () => heroes.map(hero => <Hero key={hero.id} hero={hero} > </Hero> ) return ( <div> <h1>Heroes</h1> <ul> {rows()} </ul> </div> ); } export default App;
Ho aggiunto anche una costante che utilizza la funzione useState per inizializzare lo stato memorizzato negli eroi :
... const [eroi, setEroi] = useState(heroes) const rows = () => eroi.map(hero => ...
volendo potrei impostare un elenco di array vuoto in fase di partenza:
const [eroi, setEroi] = useState([]) ...
al momento ciò mi restituirebbe una lista vuota, per ora reimpostiamola come prima
const [eroi, setEroi] = useState(heroes)
Metodo addHero
Aggiungiamo ora il metodo addHero il quale ci consentirà di aggiungere nuovi eroi alla lista:
.. const rows = () => eroi.map(hero => //... const addHero = event => { event.preventDefault(); console.log('Button clicked' , event.target); } return ( <div> <h1>Heroes</h1> <ul> {rows()} </ul> <form onSubmit={addHero}> <input/> <button type='submit'> Add</button> </form> </div> //...
Il parametro event è l’ evento che attiva la chiamata alla funzione del gestore eventi:
Il gestore eventi chiama immediatamente il metodo event.preventDefault () , che impedisce l’azione predefinita di invio di un modulo. L’azione predefinita, tra le altre cose, farebbe ricaricare la pagina.
Il target dell’evento memorizzato in event.target viene registrato nella console:
si può notare il form che abbiamo definito nel componente.
Come possiamo accedere ai dati contenuti nell’elemento di input del modulo ?
Ci sono molti modi per farlo; il primo metodo che prenderemo in esame è l’uso dei cosiddetti componenti controllati .
Aggiungiamo una costante di stato chiamandola newHero per la memorizzazione dell’input inviato dall’utente e impostiamola come attributo del valore dell’elemento di input :
//... const [newHero, setNewHero] = useState(''); const rows = () => eroi.map(hero => <Hero key={hero.id} hero={hero} > </Hero> ) const addHero = event => { event.preventDefault(); console.log('Button clicked' , event.target); } const handleHeroChange = event => { console.log (event.target.value) setNewHero(event.target.value) } return ( <div> <h1>Heroes</h1> <ul> {rows()} </ul> <form onSubmit={addHero}> <input type='text' value={newHero} onChange={handleHeroChange} /> <button type='submit'> Add</button> </form> //...
ho inserito sia il value nel tag input, il quale prende nota di ciò che l’utente andrà a scrivere , che l’evento onChange il quale richiama il metodo di callBack handleHeroChange che si occuperà di modificare lo stato dello stesso.
La proprietà target dell’oggetto event ora corrisponde all’elemento input controllato e event.target.value si riferisce al valore input di quell’elemento.
Si noti che non è stato necessario chiamare il metodo event.preventDefault () come abbiamo fatto nel gestore eventi onSubmit . Questo perché non esiste alcuna azione predefinita che si verifica su una modifica di input, a differenza di un invio del modulo.
Puoi seguire la console per vedere come viene chiamato il gestore eventi:
Ora lo stato newHero del componente App riflette il valore corrente dell’input, il che significa che possiamo completare la funzione addHero per la creazione di nuovi eroi:
//...
const addHero = event => {
event.preventDefault();
const heroObject = {
name: newHero,
date: new Date().toISOString(),
important: Math.random() > 0.5,
id: heroes.length + 1,
}
setEroi(heroes.concat(heroObject))
setNewHero('')
}
//...
Metodo concat di javascript
Per prima cosa creiamo un nuovo oggetto per l’eroe chiamato heroObject che riceverà il suo contenuto dallo stato newHero del componente . L’ ID identificativo univoco viene generato in base al numero totale di eroi. Questo metodo funziona per la nostra applicazione poiché gli eroi non vengono mai eliminati. Con l’aiuto del comando Math.random () , il nostro eroe ha una probabilità del 50% di essere contrassegnato come important.
Il nuovo eroe viene aggiunto all’elenco degli eroi utilizzando il metodo javascript concat:
setEroi(heroes.concat(heroObject))
Il metodo non muta l’ array di stato degli eroi originali , bensì crea una nuova copia dell’array con il nuovo elemento aggiunto alla fine . Questo è importante poiché non dobbiamo mai mutare lo stato direttamente in React!
Il gestore di eventi resetta anche il valore dell’elemento di input controllato chiamando il setNewHero funzione di stato di newHero:
setNewNote('')
Filtraggio degli elementi visualizzati
Aggiungiamo una nuova funzionalità alla nostra applicazione la quale ci consentirà di visualizzare solo gli eroi preferiti.
Realiziamo una costante di stato nel componente App che avrà il compito di tenere traccia degli eroi da visualizzare:
//...
const [eroi, setEroi] = useState(heroes)
const [newHero, setNewHero] = useState('')
const [showAll, setShowAll] = useState(true)
//...
Modificate il componente in modo che memorizzi un elenco di tutte gli eroi da visualizzare nella variabile heroesToShow . Le voci dell’elenco dipendono dallo stato del componente:
const [eroi, setEroi] = useState(heroes)
const [newHero, setNewHero] = useState('')
const [showAll, setShowAll] = useState(true)
const heroesToShow = showAll
? heroes
: heroes.filter(hero => hero.important === true)
//...
Attivare lo stato showAll
Di seguito ho aggiunto un button prima del form con l’evento onClick il quale richiama una funzione anonima che attiva lo stato di showAll, il codice completo di app.js:
import React, {useState} from 'react';
import './App.css';
import Hero from './components/Hero.js'
const App = () => {
const heroes = [
{
id: 1,
name: 'Hulk',
date: '2019-05-30T17:30:31.098Z',
important: true
},
{
id: 2,
name: 'Spiderman',
date: '2019-05-30T18:39:34.091Z',
important: false
},
{
id: 4,
name: 'Superman',
date: '2019-05-30T19:20:14.298Z',
important: true
}
]
const [eroi, setEroi] = useState(heroes)
const [newHero, setNewHero] = useState('')
const [showAll, setShowAll] = useState(true)
const heroesToShow = showAll
? heroes
: heroes.filter(hero => hero.important === true)
const rows = () => eroi.map(hero =>
<Hero
key={hero.id}
hero={hero}
>
</Hero>
)
const addHero = event => {
event.preventDefault();
const heroObject = {
name: newHero,
date: new Date().toISOString(),
important: Math.random() > 0.5,
id: heroes.length + 1,
}
setEroi(heroes.concat(heroObject))
setNewHero('')
}
const handleHeroChange = event => {
console.log (event.target.value)
setNewHero(event.target.value)
}
return (
<div>
<h1>Heroes</h1>
<button onClick={() => setShowAll(!showAll)}>
show {showAll ? 'important' : 'all'}
</button>
<ul>
{rows()}
</ul>
<form onSubmit={addHero}>
<input
type='text'
value={newHero}
onChange={handleHeroChange}
/>
<button type='submit'> Add</button>
</form>
</div>
);
}
export default App;
Gli eroi visualizzati sono controllati tramite un pulsante. Il gestore eventi per il pulsante è stato definito direttamente nell’attributo dell’elemento button. Il gestore eventi commuta il valore di showAll da true a false e viceversa:
() => setShowAll(!showAll)
Il testo del pulsante dipende dal valore dello stato showAll :
show {showAll ? 'important' : 'all'}
Guida a react Hooks: Aggiungere json server per acquisire i dati
Json Server è un ottimo mock server (ossia simulatore di server) da utilizzare in fase di test, questo salva e preleva dati da un file in formato json. Possiamo installarlo sia a livello globale che locale, in questo caso lo faremo globalmente.
Inoltre installeremo ,sempre tramite npm, anche il package concurrently il quale ci consente di avviare in contemporanea sia il server che l’applicazione.
Aprite il terminale , all’interno del progetto e digitate:
npm install -g json-server
quindi:
npm install concurrently --save
quindi ,nella root principale del progetto, createvi una directory chiamandola server ed al suo interno createvi il file db.json, apritelo e digitate:
"heroes": [
{
"id": 1,
"name": "Hulk",
"date": "2019-05-30T17: 30: 31.098Z",
"important": true
},
{
"id": 2,
"name": "Spiderman",
"date": "2019-05-30T17: 30: 31.098Z",
"important": true
},
{
"id": 3,
"name": "Superman",
"date": "2019-05-30T17: 30: 31.098Z",
"important": true
}
]
}
Modifichiamo quindi il file package.json che conterrà le seguenti informazioni. Notate che abbiamo modificato leggermente la sezione scripts in modo da avviare CRA e JSON-server tramite Concurrently. In particolare verrà lanciato webpack-dev-server sulla porta 3000 e JSON server sulla porta 3001:
{
"name": "heroes",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-scripts": "3.3.0"
},
"scripts": {
"start": "concurrently --kill-others \"npm run start-react\" \"npm run json-server\"",
"start-react": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"json-server": "json-server -p 3001 --watch server/db.json"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
A questo punto avviate il server:
PS E:\react\test\heroes> npm start
se tutto è andato a buon fine vi troverete con una schermata simile a quella sopra.
Sul lato sinistro dell’immagine potete notare un’alternanza di ‘[0]’ e ‘[1]’. È l’output generato da concurrently, dato che abbiamo eseguito due comandi contemporaneamente. Ogni volta che il processo, lanciato con react-scripts start, stampa qualcosa nel terminale, vedremo uno ‘[0]’ seguito dal messaggio. Vedremo invece un ‘[1]’ seguito da un messaggio quando sarà JSON-server a stampare qualcosa.
Se fate click su http://localhost:3001/heroes vi troverete davanti ad una pagina come quella sotto:
cioè il json heroes.
Se volete visualizzare il singolo eroe aggiungete l’id dopo http://localhost:3001/heroes ossia http://localhost:3001/heroes/2, visualizzerete l’eroe con id in questione.
Installare axios per React
Axios è una libreria che serve per fare chiamate asincrone, quindi ci occorre per gestire il get (lettura),il post (invio), il put(modifica) ed il delete(cancellazione) di elementi da un END POINT.
Installiamola come dipendenza:
npm install axios --save
se aprite il file package.json la ritroverete nelle dependencies:
"dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", "@testing-library/user-event": "^7.2.1", "axios": "^0.19.0", "concurrently": "^5.0.2", "react": "^16.12.0", "react-dom": "^16.12.0", "react-scripts": "3.3.0" },
importiamo ora la libreria nel file app.js per poterla utilizzare:
import axios from 'axios'
React Effect-hooks
Abbiamo già usato gli hook di stato (useState) che sono stati introdotti insieme a React versione 16.8.0 ,i quali forniscono lo stato ai componenti di React definiti come funzioni. La versione 16.8.0 introduce anche gli hook di effetti (useEffect) come una nuova funzionalità.
Gli Hook effetti consentono di eseguire effetti collaterali nei componenti delle funzioni. Il recupero dei dati, l’impostazione di un abbonamento e la modifica manuale del DOM nei componenti di React sono tutti esempi di effetti collaterali.
Pertanto, gli hook di effetti sono esattamente lo strumento giusto da utilizzare per recuperare i dati da un server.
Il componente App cambia come segue:
import React, {useState,useEffect} from 'react' import './App.css' import Hero from './components/Hero.js' import axios from 'axios' const App = () => { const [heroes, setHeroes] = useState([]) const [newHero, setNewHero] = useState('') const [showAll, setShowAll] = useState(true) useEffect(() => { console.log('effect') axios .get('http://localhost:3001/heroes') .then(response => { console.log('promise fulfilled') setHeroes(response.data) }) }, []) console.log('render', heroes.length, 'notes') const heroesToShow = showAll ? heroes : heroes.filter(hero => hero.important === true) const rows = () => heroes.map(hero => <Hero key={hero.id} hero={hero} > </Hero> ) const addHero = event => { event.preventDefault(); const heroObject = { name: newHero, date: new Date().toISOString(), important: Math.random() > 0.5, id: heroes.length + 1, } setHeroes(heroes.concat(heroObject)) setNewHero('') } const handleHeroChange = event => { console.log (event.target.value) setNewHero(event.target.value) } return ( <div> <h1>Heroes</h1> <button onClick={() => setShowAll(!showAll)}> show {showAll ? 'important' : 'all'} </button> <ul> {rows()} </ul> <form onSubmit={addHero}> <input type='text' placeholder='Add Hero...' value={newHero} onChange={handleHeroChange} /> <button type='submit'> Add</button> </form> </div> ); } export default App;
Per il momento è tutto, finisce qui la prima parte del tutorial, nel prossimo articolo(guida a react hooks parte 2)/ andremo a completare la demo. Vedremo come aggiungere, modificare o cancellare eroi dalla nostra lista. Aggiungeremo lo stile ed un server per la gestione delle chiamate asincrone all’End Point.
Se vuoi approfondire lo studio di React puoi seguire il mio corso su Udemy: React e Redux: la guida completa.