Blog

react con laravel

React e Laravel: realizzare un sistema Crud

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:

react e laravel

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:

react e laravel

 

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_at 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!

react e laravel

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:

laravel react

provate a richiedere una risorsa in particolare , http://reactapi.test/api/products/2 ad esempio:

laravel con react

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

 

fatal error allowed memory size

è relativo ad una dimensione del file di allocazione di composer risolvibile digitando di seguito da terminale:

php composer.phar --self-update
// or
composer self-update

elimina la cartella vendor:

rm -rf vendor/laravel

aggiorna composer:

composer update

ora digita:

php -d memory_limit=-1 /usr/local/bin/composer update

ed 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

Se sei all’asciutto di React puoi seguire il mio corso su Udemy.
Un’applicazione React è composta da componenti, questi sono la struttura più importante della stessa , si trovano all’interno di una directory dedicata.

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:

laravel and react application

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 currentProductcon un valore null iniziale .
  • Quando si fa clic sul titolo di un prodotto, this.state.currentProductviene 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 createProductlo 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 (sia this.state.productsthis.state.currentProduct)

Innanzitutto creiamo il componente AddProduct.

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 AddProductdeve 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, AddProductnel 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 onAddgestore 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 productscurrentProductsviene 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:

...
/* 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:

laravel and react application

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!

Se volete Approfondire le conoscenze su Laravel 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!

Lucio Ticali

Chi è ?

Lucio Ticali è un Web & App developer con la passione per il web marketing,si occupa anche di tecniche di indicizzazione del sito e di promozione dello stesso (SEO e SEM).

Lascia la tua opinione