Tempo di lettura: 24 minuti
React e Laravel sono due tre le tecnologie di sviluppo web più diffuse utilizzate per la creazione di applicazioni web moderne. Laravel è un framework PHP lato server, mentre React è una libreria JavaScript lato client.
Con Laravel si possono creare progetti completi quali e-commerce, blog, gestionali, sito vetrina, ecc… Tuttavia spesso viene utilizzato per realizzare ReastFull Api.
React e Laravel: RestFull Api
API sta per Application Program Interface . In poche parole, un’API è un’interfaccia che gli sviluppatori utilizzano per comunicare con le applicazioni.
L’API agisce proprio come un middleware . Quando inviamo richieste a un’API, essa controlla le richieste. Se le richieste sono consentite, i dati verranno restituiti. Vengono restituite anche risposte adeguate per farci conoscere il risultato delle nostre richieste.
Utilizzando le API, possiamo creare efficacemente un servizio di backend che supporta molti tipi di applicazioni. Gli sviluppatori possono cambiare frequentemente l’aspetto delle loro app senza preoccuparsi di rompere le app.
REST sta per Representational State Transfer . È uno stile di architettura web. Fondamentalmente, REST è solo un insieme di accordi e vincoli su come i componenti dovrebbero lavorare insieme.
Quando le API utilizzano l’architettura REST, vengono chiamate API REST ( note anche come API RESTful ).
Una tipica API REST ha i seguenti vincoli :
- Client – server : server (back end) e client (front end) possono essere sviluppati indipendentemente.
- Stateless : lo stato della sessione deve essere archiviato sul client. I dati del client non devono essere archiviati sul server tra le richieste.
- Memorizzabile nella cache: il client può memorizzare nella cache le risposte per migliorare la scalabilità e le prestazioni.
L’API REST utilizza le richieste HTTP per comunicare con i server. Ogni richiesta specifica un certo verbo HTTP nell’intestazione della richiesta, come ad esempio:
GET /posts HTTP/1.1
Esistono molti verbi HTTP , ma i più popolari per la creazione di API REST sono:
- GET
- POST
- PUT
- DELETE
tutto ciò che serve , in pratica , per realizzare un sistema di CRUD /CREATE, READ,UPDATE,DELETE)
Perchè Laravel e React?
Nello sviluppo delle applicazioni moderne si opta nella soluzione della separazione delle responsabilità. In parole povere si utilizza un framework o libreria js che gestisca la parte front end delle applicazioni.
Oggi tecnologie come React, Angular o Vue.js ci consentono di realizzare single page application offrendo un’esperienza di navigazione e prestazioni maggiori rispetto a quello che non offre una tecnologia realizzata in php, come Laravel.
Inoltre nello sviluppo in team ciò da la possibilità agli sviluppatori di dedicarsi ad una parte del codice piuttosto che ad un’altra.
Laravel e React si adattano benissimo nonostante esiste una documentazione con supporto con Vue.js, in quanto i due framework condividono la stessa filosofia basata sulla semplificazione della realizzazione dei progetti, ma questo lo discuteremo in altra sede.
Prerequisiti
Prima di continuare do per scontato che , dato che seguite questo tutorial, abbiate quanto meno le basi di Laravel e React, quindi non scenderò nei particolari.
In caso contrario vi invito a leggervi le guide React e Laravel.
Scaricatevi, se non lo avete Git Hub e prima di continuare con l’installazione di Laravel leggetevi questa guida.
React e Laravel: installare Laravel
Aprite Git Bash ed installate laravel installer mediante questo comando:
composer global require "laravel/installer"
quindi installate l’applicazione laravel, scegliete una directory a vostro piacere , da terminale spostatevi all’interno di essa mediante comando cd e digitate:
laravel new reactapi
attendete qualche istante , il tempo che venga scaricato l’intero progetto.
Una volta fatto spostatevi all’interno del progetto :
cd reactapi
quindi avviate il server per verificare che sia tutto a posto:
php artisan serve
se accedete all’indirizzo http://127.0.0.1:8000/ dovreste vedere:
ottimo è tutto a posto!
tutta via utilizzeremo il server virtuale Homestead, lo imposteremo come fatto in questa guida, quindi aprite il file homestead.yaml e mappate l’applicazione creata:
... - map: reactapi.test to: /home/vagrant/Code/Project/public/reactapi/public ...
aprite il file hosts e date i permessi:
192.168.10.10 reactapi.test
Non mi dilungo oltre è tutto spiegato nella guida ‘installare laravel homestead su windows‘
quindi da terminale, all’interno del progetto reactapi digitate:
vagrant up --provision
dopo aver avviato vagrant quindi digitate http://reactapi.test/ vi troverete:
Crea il database
Una volta avviato homestead possiamo avviare la VM ,digitate da terminale :
vagrant ssh
quindi spostiamoci all’interno del progetto con:
cd Code/Project/public/reactapi
ora digitate:
mysql -u homestead -p
quando vi chiede la password digitate secret.
digitate
create database reactapi_db;
date invio e per prova digitate:
show databases;
vi verrà restituito:
mysql> show databases; +--------------------+ | Database | +--------------------+ | angularbook | | homestead | | ijdb | | information_schema | | mysql | | performance_schema | | reactapi_db | | sys | | ticket | +--------------------+ 9 rows in set (0.02 sec)
ottimo in lista c’è anche il db appena creato non ci resta che configurare la connessione in laravel.
Con il vostro ide aprite il file .env (si trova nell root principale del progetto) e modificate come di seguito:
... DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=reactapi_db DB_USERNAME=homestead DB_PASSWORD=secret ...
il file .env è un file di configurazione importantissimo in laravel.
Realizza il model prodotto
Laravel si basa sul paradigma mvc ossia model view controller, ancora una volta se siete all’asciutto vi esorto a leggere la guida oppure se volete a seguire il corso su Udemy: laravel la guida completa.
Da terminale, sempre all’interno della directory principale del progetto, digitate:
vagrant@homestead:~/Code/Project/public/reactapi$ php artisan make:model Product -m
questo comando ci consente di realizzare il model Product ed il -m mi crea la migration.
vagrant@homestead:~/Code/Project/public/reactapi$ php artisan make:model Product -m Model created successfully. Created Migration: 2021_01_17_132501_create_products_table ^C vagrant@homestead:~/Code/Project/public/reactapi$
bene quindi aprite il file di migrazione sotto database/migrations/2021_01_17_132501_create_products_table.php:
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateProductsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('products', function (Blueprint $table) { $table->id(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('products'); } }
Il metodo di up
viene chiamato durante la migrazione di nuove tabelle e colonne nel database, mentre il metodo down
viene richiamato durante il rollback di una migrazione. Abbiamo creato uno schema per una tabella con tre righe: id,
created_at,
and updated_at.
Il metodo $table-> timestamps ()
è responsabile del mantenimento delle colonne created_a
t e updated_at.
Quindi modificate come di seguito:
... Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); $table->string('title'); $table->text('description'); $table->integer('price'); $table->boolean('availability'); }); ...
in pratica gli diciamo di realizzare alcuni campi che possono essere di tipo number, text, string, boolean, ecc…
Inviamola al database realizzato in precedenza con il comando:
php artisan migrate
dovreste ottenere:
vagrant@homestead:~/Code/Project/public/reactapi$ php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (172.25ms) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (153.77ms) Migrating: 2019_08_19_000000_create_failed_jobs_table Migrated: 2019_08_19_000000_create_failed_jobs_table (388.25ms) Migrating: 2021_01_17_132501_create_products_table Migrated: 2021_01_17_132501_create_products_table (44.75ms) vagrant@homestead:~/Code/Project/public/reactapi$
Per convenzione laravel assume che il model Product sia associato alla tabella Products, in pratica la crea con il solito nome del model ma in plurale. Se volessimo cambiarne il nome , prima del migrate, dovremmo aprire il file model e digitare:
app/Models/Product.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Product extends Model { protected $table = 'custom_prodotti'; }
comunque in questa guida assumerò la convenzione laravelliana, per cui omettete.
Sempre nel model prodotti andiamo a settare i permessi di modifica nei campi della tabella:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Product extends Model { //protected $table = 'custom_prodotti'; protected $fillable = ['title','description','price','availaility']; }
in alternativa potevamo dire , rendi tutti modificabili tranne il campo id con questa istruzione:
... protected $guarded = ['id']; ...
React e laravel: il Seeder
Laravel , in fase di test e sviluppo, ci consente di riempire le tabelle con dati finti con l’unico scopo di testare il loro funzionamento anche ai fini del layout delle pagine.
Da terminale digitate:
php artisan make:seeder ProductsTableSeeder
ora apritelo in database/seeders/ProductsTableSeeder.php e modificate:
<?php namespace Database\Seeders; use App\Models\Product; use Illuminate\Database\Seeder; class ProductsTableSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { $faker = \Faker\Factory::create(); // Create 50 product records for ($i = 0; $i < 50; $i++) { Product::create([ 'title' => $faker->text($maxNbChars = 80), 'description' => $faker->paragraph, 'price' => $faker->randomNumber(2), 'availability' => $faker->boolean(50) ]); } } }
ok ora inviatelo con il comando:
php artisan db:seed --class=ProductsTableSeeder
avrete una tabella popolata!
Come gestionale dei miei db realizzati in Homestead utilizzo il software MySQL Workbench, il quale si abbina perfettamente al server, ve lo consiglio.
Gestire le rotte
Quando si realizza un end point in laravel, quindi un API le rotte verranno gestite dal file routes/api.php , mentre il file routes/web.php si occupa di gestire le rotte comuni e quindi non protette.
Aprite routes/api.php e digitate:
<?php use App\Models\Product; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Route::get('products', function () { return response(Product::all(),200); }); Route::get('products/{product}', function ($productId) { return response(Product::find($productId), 200); }); Route::post('products', function(Request $request) { $resp = Product::create($request->all()); return $resp; }); Route::put('products/{product}', function(Request $request, $productId) { $product = Product::findOrFail($productId); $product->update($request->all()); return $product; }); Route::delete('products/{product}',function($productId) { Product::find($productId)->delete(); return 204; });
Laravel e React: il controller
Al momento abbiamo fatto si che il file di rotta api.php gestisca sia i percorsi che la gestione delle richieste. Questa è una cattiva prassi, dobbiamo far si che le richieste vengano gestite dal controller, quindi creiamolo:
php artisan make:controller ProductsController -r
flaggandolo con -r gli diciamo di aggiungere tutte le risorse atte alla gestione crud.
Apritelo sotto app/HTTP/Controllers/ProductsController.php e modificate:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class ProductsController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { // } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { // } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy($id) { // } }
in
<?php namespace App\Http\Controllers; use App\Models\Product; use Illuminate\Http\Request; class ProductsController extends Controller { /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index() { return Product::all(); } public function show(Product $product) { return $product; } public function store(Request $request) { $product = Product::create($request->all()); return response()->json($product, 201); } public function update(Request $request, Product $product) { $product->update($request->all()); return response()->json($product, 200); } public function delete(Product $product) { $product->delete(); return response()->json(null, 204); } }
quindi modifichiamo il file route/api.php:
<?php use App\Models\Product; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); Route::get('products', 'App\Http\Controllers\ProductsController@index'); Route::get('products/{product}', 'App\Http\Controllers\ProductsController@show'); Route::post('products','App\Http\Controllers\ProductsController@store'); Route::put('products/{product}','App\Http\Controllers\ProductsController@update'); Route::delete('products/{product}', 'App\Http\Controllers\ProductsController@delete');
Come di certo avrete notato, ho iniettato un’istanza di prodotto nei metodi del controller. Questo è un esempio di impegno implicito di Laravel. Il framework cerca di abbinare il nome di prodotto modello $ product $
con il nome del segmento URI {product}.
Se viene trovata una corrispondenza, un’istanza del modello di prodotto viene iniettata nelle azioni del controller. Se il database non dispone di un prodotto, restituisce un errore 404. Il risultato finale è lo stesso di prima ma con meno codice.
Testiamo i percorsi con Postman. Apritelo e digitate http://reactapi.test/api/products con chiamata get chiaramente, se tutto funziona vi verrà restituito un file json:
provate a richiedere una risorsa in particolare , http://reactapi.test/api/products/2 ad esempio:
come potete notare mi verrà restituito la risorsa con id pari a 2.
Laravel e React: piccolo sommario
Benissimo ad ora siamo riusciti a realizzare un End Point in Laravel per la gestione di eventuali prodotti. Abbiamo visto le rotte, il model ed il controller fino a testare l’API con postman.
Siamo a metà dell’opera, ora non ci resta che implementare la parte Front End con React. Vedremo come configurarlo in ambiente Laravel grazie a Laravel Mix.
Come configurare React in Laravel
Laravel Mix è stato introdotto in Laravel già dalla versione 5.4 ed è, senza dubbio, il miglior modo per collegare libreria e framework.
Da terminale digitate, sempre spostandovi all’interno della root principale di reactapi:
composer require laravel/ui:^2.4
quindi come recita la documentazione ufficiale di Laravel:
php artisan ui react --auth
nota con il –auth abbiamo generato anche lo scafolding (impalcatura) relativo alla gestione delle autenticazioni.
Nota: se anche a voi come a me vi dasse un errore del genere:
Composer: Allowed memory size error when installing package
è relativo ad una dimensione del file di allocazione di composer risolvibile digitando di seguito da terminale:
php composer.phar --self-update // or composer self-updateelimina la cartella vendor:
rm -rf vendor/laravelaggiorna composer:
composer updateora digita:
php -d memory_limit=-1 /usr/local/bin/composer updateed il problema dovrebbe essere risolto.
Se aprite resources/js/components/ noterete un file Example.js:
import React from 'react'; import ReactDOM from 'react-dom'; function Example() { return ( <div className="container"> <div className="row justify-content-center"> <div className="col-md-8"> <div className="card"> <div className="card-header">Example Component</div> <div className="card-body">I'm an example component!</div> </div> </div> </div> </div> ); } export default Example; if (document.getElementById('example')) { ReactDOM.render(<Example />, document.getElementById('example')); }
è un component React creato da laravel. Rinominatelo in Main.js e modificate come di seguito:
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; /* An example React component */ class Main extends Component { render() { return ( <div> <h3>All Products</h3> </div> ); } } export default Main; /* The if statement is required so as to Render the component on pages that have a div with an ID of "root"; */ if (document.getElementById('root')) { ReactDOM.render(<Main />, document.getElementById('root')); }
ora rendiamolo accessibile alla nostra view, quindi apri resources/views/welcome.blade.php e modifica:
<!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel React application</title> <link href="{{mix('css/app.css')}}" rel="stylesheet" type="text/css"> </head> <body> <h2 style="text-align: center"> Laravel and React application </h2> <div id="root"></div> <script src="{{mix('js/app.js')}}" ></script> </body> </html>
compila il file digitando da terminale:
npm run watch
eseguendo run watch abiliti automaticamente il package.json alla compilazione automatica una volta fatta una modifica, in alternativa puoi utilizzare npm run dev. Per uscire dalla compilazione automatica da tastiera premi ctrl+c.
Sviluppo dell’applicazione React
I componenti ti consentono di suddividere l’interfaccia utente in parti indipendenti e riutilizzabili e di pensare a ciascuna parte separatamente. Concettualmente, i componenti sono come le funzioni JavaScript. Accettano input arbitrari (chiamati “oggetti di scena”) e restituiscono elementi React che descrivono cosa dovrebbe apparire sullo schermo.
– Documenti ufficiali di React
Per l’applicazione che stiamo realizzando, inizieremo con un componente di base che visualizza tutti i prodotti restituiti dal server. Utilizzeremo il Main.js component il quale si occuperà di:
- Memorizzare i dati del prodotto nel loro stato.
- Visualizzare i dati del prodotto.
React non è un framework a tutti gli effetti, quindi la libreria non ha alcuna funzionalità AJAX da sola. Userò fetch()
, che è un’API JavaScript standard per il recupero dei dati dal server. Ma ci sono tantissime alternative per effettuare chiamate AJAX al server come ad esempio axios.
Aprite quindi resources/js/components/Main.js e modificate come di seguito:
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; /* An example React component */ /* Main Component */ class Main extends Component { constructor() { super(); //Initialize the state in the constructor this.state = { products: [], } } /*componentDidMount() is a lifecycle method * that gets called after the component is rendered */ componentDidMount() { /* fetch API in action */ fetch('/api/products') .then(response => { return response.json(); }) .then(products => { //Fetched product is stored in the state this.setState({ products }); }); } renderProducts() { return this.state.products.map(product => { return ( /* When using list you need to specify a key * attribute that is unique for each list item */ <li key={product.id} > { product.title } </li> ); }) } render() { /* Some css code has been removed for brevity */ return ( <div> <ul> { this.renderProducts() } </ul> </div> ); } } export default Main; /* The if statement is required so as to Render the component on pages that have a div with an ID of "root"; */ if (document.getElementById('root')) { ReactDOM.render(<Main />, document.getElementById('root')); }
compilate con npm run dev, o watch come preferite.
Ecco il risultato:
Benissimo, la pagina restituisce i titoli dei prodotti ora la renderemo interattiva, ossia cliccabile per poter vedere il dettaglio del prodotto.
Visualizzare il dettaglio del prodotto
In questa sezione andremo a trattare:
- Uno stato per monitorare il prodotto su cui è stato fatto clic. Chiamiamolo
currentProduct
con un valorenull
iniziale . - Quando si fa clic sul titolo di un prodotto,
this.state.currentProduct
viene aggiornato. - I dettagli del prodotto in questione sono visualizzati a destra. Finché non viene selezionato un prodotto, viene visualizzato il messaggio “Nessun prodotto selezionato”.
resources/js/components/Main.js
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; /* An example React component */ /* Main Component */ class Main extends Component { constructor() { super(); /* currentProduct keeps track of the product currently * displayed */ this.state = { products: [], currentProduct: null } } componentDidMount() { //code omitted for brevity } renderProducts() { return this.state.products.map(product => { return ( //this.handleClick() method is invoked onClick. <li onClick={ () =>this.handleClick(product)} key={product.id} > { product.title } </li> ); }) } handleClick(product) { //handleClick is used to set the state this.setState({currentProduct:product}); } render() { /* Some css code has been removed for brevity */ return ( <div> <ul> { this.renderProducts() } </ul> </div> ); } } export default Main; /* The if statement is required so as to Render the component on pages that have a div with an ID of "root"; */ if (document.getElementById('root')) { ReactDOM.render(<Main />, document.getElementById('root')); }
Qui abbiamo aggiunto createProduct
lo stato e inizializzato con il valore null
. La riga onClick={ () =>this.handleClick(product) }
richiama il metodo handleClick()
quando si fa clic sull’elemento dell’elenco. Il metodo handleClick()
aggiorna lo stato di currentProduct
.
Ora per visualizzare i dati del prodotto, possiamo renderli all’interno del componente Main o creare un nuovo componente. Come accennato in precedenza, la suddivisione dell’interfaccia utente in componenti più piccoli è il modo in cui React esegue le operazioni. Quindi creeremo un nuovo componente e lo denomineremo Prodotto.
Il componente Prodotto è nidificato all’interno del componente Main. Il componente Main passa il suo stato come oggetti di scena. Il componente Prodotto accetta questi oggetti di scena come input e restituisce le informazioni pertinenti.
resources/js/components/Main.js
... render() { return ( /* The extra divs are for the css styles */ <div> <div> <h3> All products </h3> <ul> { this.renderProducts() } </ul> </div> <Product product={this.state.currentProduct} /> </div> ); } ...
Non dimenticarti di importare Product in Main.js:
... import Product from './Product'; ...
resources/js/components/Product.js
import React, { Component } from 'react'; /* Stateless component or pure component * { product } syntax is the object destructing */ const Product = ({product}) => { const divStyle = { /*code omitted for brevity */ } //if the props product is null, return Product doesn't exist if(!product) { return(<div style={divStyle}> Product Doesnt exist </div>); } //Else, display the product data return( <div style={divStyle}> <h2> {product.title} </h2> <p> {product.description} </p> <h3> Status {product.availability ? 'Available' : 'Out of stock'} </h3> <h3> Price : {product.price} </h3> </div> ) } export default Product ;
Diamo un pò di stile con le classi di bootstrap:
resources/views/welcome.blade.php
<!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel React application</title> <link href="{{mix('css/app.css')}}" rel="stylesheet" type="text/css"> </head> <body> <h2 class="text-center"> Laravel and React application </h2> <div class="container-fluid" id="root"></div> <script src="{{mix('js/app.js')}}" ></script> </body> </html>
resources/js/components/Main.js
... renderProducts() { return this.state.products.map(product => { return ( /* When using list you need to specify a key * attribute that is unique for each list item */ <li className="list-group-item" onClick={ () => this.handleClick(product)} key={product.id}> {product.title} </li> ); }) } handleClick(product) { this.setState({currentProduct: product}) } render() { return ( /* The extra divs are for the css styles */ <div className={'row'}> <div className={'col-md-6'}> <h3> All products </h3> <ul className="list-group"> {this.renderProducts()} </ul> </div> <Product product={this.state.currentProduct}/> </div> ); } } ...
resources/js/components/Product.js
... if(!product) { return(<div className={'col-md-6 jumbotron'}> Product Doesnt exist </div>); } //Else, display the product data return( <div className={'col-md-6 jumbotron'}> <h2 className={'display-4'}> {product.title} </h2> <p className={'lead'}> {product.description} </p> <h3> Status {product.availability ? 'Available' : 'Out of stock'} </h3> <h3> Price : {product.price} </h3> </div> ) } ...
Niente di eccezionale, potete personalizzarlo come meglio credete, il risultato è questo:
Aggiunta di un nuovo prodotto
Fino ad ora abbiamo implementato il front-end corrispondente al recupero di tutti i prodotti e alla loro visualizzazione. Ora non ci resta che implementare un modulo che ci consenta di aggiungere nuovi prodotti alla nostra lista.
Quindi svilupperemo:
- Un nuovo componente stateful che esegue il rendering dell’interfaccia utente per un modulo di input. Lo stato del componente contiene i dati del modulo.
- Al momento dell’invio, il componente figlio passa lo stato al componente Main utilizzando un callback.
- Il componente Main ha un metodo, diciamo
handleNewProduct()
, che gestisce la logica per avviare una richiesta POST. Dopo aver ricevuto la risposta, il componente Main aggiorna il proprio stato (siathis.state.products
ethis.state.currentProduct
)
Innanzitutto creiamo il componente AddProduct.
resources/js/component/AddProduct.js
import React, {Component} from 'react'; class AddProduct extends Component { constructor(props) { super(props); /* Initialize the state. */ this.state = { newProduct: { title: '', description: '', price: 0, availability: 0 } } //Boilerplate code for binding methods with `this` this.handleSubmit = this.handleSubmit.bind(this); this.handleInput = this.handleInput.bind(this); } /* This method dynamically accepts inputs and stores it in the state */ handleInput(key, e) { /*Duplicating and updating the state */ var state = Object.assign({}, this.state.newProduct); state[key] = e.target.value; this.setState({newProduct: state }); } /* This method is invoked when submit button is pressed */ handleSubmit(e) { //preventDefault prevents page reload e.preventDefault(); /*A call back to the onAdd props. The current *state is passed as a param */ this.props.onAdd(this.state.newProduct); } render() { const divStyle = { /*Code omitted for brevity */ } return( <div> <h2> Add new product </h2> <div style={divStyle}> /*when Submit button is pressed, the control is passed to *handleSubmit method */ <form onSubmit={this.handleSubmit}> <label> Title: { /*On every keystroke, the handeInput method is invoked */ } <input type="text" onChange={(e)=>this.handleInput('title',e)} /> </label> <label> Description: <input type="text" onChange={(e)=>this.handleInput('description',e)} /> </label> { /* Input fields for Price and availability omitted for brevity */} <input type="submit" value="Submit" /> </form> </div> </div>) } } export default AddProduct;
Il componente fondamentalmente esegue il rendering di un modulo di input e tutti i valori di input vengono memorizzati in state ( this.state.newProduct
). Quindi, durante l’invio del modulo, il handleSubmit()
metodo viene richiamato. Ma AddProduct
deve comunicare le informazioni al genitore e lo facciamo utilizzando una richiamata.
Il componente Main, che è il genitore, passa un riferimento a una funzione come oggetti di scena. Il componente figlio, AddProduct
nel nostro caso, richiama questo props per notificare al genitore il cambiamento di stato. Quindi la riga this.props.onAdd(this.state.newProduct);
è un esempio di callback che notifica al componente principale del nuovo prodotto.
Ora, all’interno del componente Main, dichiareremo <AddProduct />
quanto segue:
<AddProduct onAdd={this.handleAddProduct} />
Il onAdd
gestore di eventi è concatenato al handleAddProduct()
metodo del componente . Questo metodo ospita il codice per effettuare una richiesta POST al server. Se la risposta indica che il prodotto è stato creato con successo, lo stato di products
e currentProducts
viene aggiornato.
handleAddProduct(product) { product.price = Number(product.price); /*Fetch API for post request */ fetch( 'api/products/', { method:'post', /* headers are important*/ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(product) }) .then(response => { return response.json(); }) .then( data => { //update the state of products and currentProduct this.setState((prevState)=> ({ products: prevState.products.concat(data), currentProduct : data })) }) }
Non dimenticare di associare il metodo handleProduct
alla classe utilizzando this.handleAddProduct = this.handleAddProduct.bind(this);
nel costruttore:
resources/js/component/Main.js
... /* Main Component */ class Main extends Component { constructor() { super(); //Initialize the state in the constructor this.state = { products: [], currentProduct: null, } this.handleAddProduct = this.handleAddProduct.bind(this); } ...
Main.js aggiornato:
import React, {Component} from 'react'; import ReactDOM from 'react-dom'; import Product from './Product'; import AddProduct from './AddProduct'; /* An example React component */ /* Main Component */ class Main extends Component { constructor() { super(); //Initialize the state in the constructor this.state = { products: [], currentProduct: null, } this.handleAddProduct = this.handleAddProduct.bind(this); } /*componentDidMount() is a lifecycle method * that gets called after the component is rendered */ componentDidMount() { /* fetch API in action */ fetch('/api/products') .then(response => { return response.json(); }) .then(products => { //Fetched product is stored in the state this.setState({products}); }); } renderProducts() { return this.state.products.map(product => { return ( /* When using list you need to specify a key * attribute that is unique for each list item */ <li className="list-group-item" onClick={ () => this.handleClick(product)} key={product.id}> {product.title} </li> ); }) } handleClick(product) { this.setState({currentProduct: product}) } handleAddProduct(product) { product.price = Number(product.price); /*Fetch API for post request */ fetch( 'api/products/', { method:'post', /* headers are important*/ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify(product) }) .then(response => { return response.json(); }) .then( data => { //update the state of products and currentProduct this.setState((prevState)=> ({ products: prevState.products.concat(data), currentProduct : data })) }) } render() { return ( /* The extra divs are for the css styles */ <div className={'row'}> <div className={'col-md-6'}> <h3> All products </h3> <ul className="list-group"> {this.renderProducts()} </ul> </div> <Product product={this.state.currentProduct}/> <AddProduct onAdd={this.handleAddProduct} /> </div> ); } } export default Main; /* The if statement is required so as to Render the component on pages that have a div with an ID of "root"; */ if (document.getElementById('root')) { ReactDOM.render(<Main/>, document.getElementById('root')); }
ecco il render finale:
Aggiornare un prodotto
Il nostro CRUD ancora non è completo in quanto manca la possibilità di aggiornare ed eliminare un prodotto.
Ve lo lascio come esercizio!!
Happy Coding
Se volete Approfondire le conoscenze su REACT e REDUX potete seguire il corso completo su Udemy sempre scontato a 9.99/12.99 € , inoltre, nel momento in cui non foste soddisfatti, avete la possibilità di ottenere il rimborso completo dello stesso entro 30 giorni dalla data di acquisto!