Advertisement
  1. Code
  2. React

Crea una aplicación React con Laravel RESTful Back End: Parte 2, React

Scroll to top
Read Time: 16 min
This post is part of a series called Build a React App with Laravel Backend.
Build a React App With a Laravel RESTful Back End: Part 1, Laravel 9 API

() translation by (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

Esta es la segunda y última parte de la serie sobre la creación de una aplicación React con un Back End en Laravel. En la primera parte de la serie, creamos una API RESTful utilizando Laravel para una aplicación básica de listado de productos. En este tutorial, desarrollaremos la interfaz utilizando React.

También consideraremos todas las opciones disponibles para cerrar la brecha entre Laravel y React. No necesitas haber seguido la primera parte de la serie para comprender este tutorial. Si estás aquí para ver cómo reaccionan React y Laravel, puedes, de hecho, evitar la primera parte. Deberías dirigirte a GitHub, clonar el repositorio y llevar a cabo la recapitulación rápida a continuación para comenzar.

Un resumen rápido

En el tutorial anterior, desarrollamos una aplicación Laravel que responde a las llamadas API. Creamos rutas, un controlador y un modelo para la aplicación de listado simple de productos. Dado que el trabajo del controlador era devolver una respuesta a las solicitudes HTTP, la sección de la vista (plantilla) se omitió por completo.

Luego tocamos técnicas para el manejo y validación de excepciones usando Laravel. Al final del tutorial, teníamos una API de back-end en Laravel. Ahora podemos usar esta API para crear aplicaciones tanto para la web como para una amplia gama de dispositivos móviles.

En este tutorial, cambiaremos nuestro enfoque hacia el diseño. La primera mitad del tutorial consiste en configurar React en un entorno Laravel. También te presentaré a Laravel Mix (compatible con Laravel 5.4 y posterior), es una API para compilar recursos. En la segunda mitad del tutorial, comenzaremos a construir una aplicación React desde cero.

Configuración de React en Laravel

Laravel Mix se introdujo en Laravel 5.4, y actualmente es la forma ideal de conectar React y Laravel. Con Laravel 5.5, todo el proceso se hizo mucho más fácil. He descrito ambos métodos a continuación.

Uso del comando React Preset (Laravel 5.5)

Laravel 5.5 tiene una nueva característica que permite andamiar el código de los componentes React utilizando el comando preset react. En versiones anteriores de Laravel, configurar React dentro de Laravel no era tan fácil. Si estás ejecutando la última versión de Laravel, ejecuta el siguiente comando para agregar un ajuste preestablecido React a tu proyecto.

1
php artisan preset react

Laravel se envía por defecto con el preajuste Vue, y el comando anterior reemplaza todas las instancias de Vue con React. Curiosamente, si no necesitas un ajuste preestablecido, puedes eliminarlo por completo con el comando php artisan preset none.

Si todo va bien, esto debería aparecer en tu terminal.

1
React scaffolding installed successfully.
2
Please run "npm install && npm run dev" to compile your fresh scaffolding.

En el fondo, Laravel usa Laravel Mix, el cual es un contenedor suave para webpack. Webpack, como ya sabrás, es un paquete de módulos. Resuelve todas las dependencias del módulo y genera los recursos estáticos necesarios para JavaScript y CSS. React requiere un paquete de módulos para funcionar, y webpack encaja perfectamente en ese rol. Así que, Laravel Mix es la capa que se encuentra sobre el webpack y hace que sea más fácil usar webpack en Laravel.

Una mejor comprensión de cómo funciona Laravel Mix es importante si necesitas personalizar la configuración del webpack en otro momento. El comando React preestablecido no nos da información sobre cómo funcionan las cosas en segundo plano. Así que eliminemos el preajuste React y recordemos los pasos manualmente.

Método manual (Laravel 5.4)

Si estás ejecutando Laravel 5.4, o si tienes curiosidad por ver cómo se configura Laravel Mix, estos son los pasos que debes seguir:

Instala reactreact-dom y babel-preset-react usando npm. También podría ser una buena idea tener yarn instalado. No es ningún secreto que Laravel y React prefieren yarn sobre el npm.

Dirígete a webpack.mix.js, ubicado dentro del directorio raíz de tu proyecto Laravel. Este es el archivo de configuración donde declaras cómo deben compilarse tus recursos. Reemplaza la línea mix.js('resources/assets/js/app.js', 'public/js'); con mix.react('resources/assets/js/app.js', 'public/js');. app.js es el punto de entrada para nuestros archivos JavaScript, y los archivos compilados se ubicarán dentro de public/js. Ejecuta npm install en la terminal para instalar todas las dependencias.

A continuación, ve a resources/assets/js. Ya hay una carpeta de componentes y un par de otros archivos JavaScript. Los componentes de React entrarán en el directorio de componentes. Elimina el archivo Example.vue existente y crea un nuevo archivo para un componente React de muestra.

resources/assets/js/component/Main.js

1
import React, { Component } from 'react';
2
import ReactDOM from 'react-dom';
3
4
/* An example React component */
5
class Main extends Component {
6
    render() {
7
        return (
8
            <div>
9
                <h3>All Products</h3>

10
            </div>

11
        );
12
    }
13
}
14
15
export default Main;
16
17
/* The if statement is required so as to Render the component on pages that have a div with an ID of "root";  

18
*/
19
20
if (document.getElementById('root')) {
21
    ReactDOM.render(<Main />, document.getElementById('root'));
22
}

Actualiza app.js para eliminar todo el código relacionado con Vue e importa el componente React en su lugar.

resources/assets/js/app.js

1
require('./bootstrap');
2
3
/* Import the Main component */
4
import Main from './components/Main';

Ahora, solo tenemos que hacer que los recursos sean accesibles para la Vista. Los archivos de vista se encuentran dentro del directorio resources/views. Agreguemos una etiqueta <script> a welcome.blade.php, que es la página predeterminada que se muestra cuando navegas a localhost:8000/. Elimina el contenido del archivo de vista y cámbialo por el siguiente código: 

resources/views/welcome.blade.php

1
<!doctype html>
2
<html lang="{{ app()->getLocale() }}">
3
    <head>
4
        <meta charset="utf-8">
5
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
        <meta name="viewport" content="width=device-width, initial-scale=1">
7
        <title>Laravel React application</title>
8
        <link href="{{mix('css/app.css')}}" rel="stylesheet" type="text/css">
9
    </head>
10
    <body>
11
    <h2 style="text-align: center"> Laravel and React application </h2>
12
        <div id="root"></div>
13
        <script src="{{mix('js/app.js')}}" ></script>
14
    </body>
15
</html>

Finalmente, ejecuta npm run dev o yarn run dev para compilar los recursos. Si visitas localhost:8000, deberías ver:

Laravel and React running togetherLaravel and React running togetherLaravel and React running together
React incrustado dentro de la vista de Laravel.

Package.json tiene un script de vigilancia que compila automáticamente los recursos cuando se detectan cambios. Para habilitar este modo, ejecuta npm run watch.

Felicitaciones, has configurado con éxito React para trabajar con Laravel. Ahora, vamos a crear algunos componentes React para la interfaz.

Desarrollando la aplicación React

Si eres nuevo en React, encontrarás que el resto del tutorial es un poco desafiante. Recomiendo tomar el curso React Crash Course para principiantes para familiarizarte mejor con los conceptos de React. ¡Empecemos!

Una aplicación React se basa en componentes. Los componentes son la estructura más importante en React, y tenemos un directorio dedicado para los componentes.

Los componentes te permiten dividir la interfaz de usuario en piezas independientes y reutilizables, y pensar en cada pieza aisladamente. Conceptualmente, los componentes son como las funciones de JavaScript. Aceptan entradas arbitrarias (llamadas "props") y devuelven elementos React que describen lo que debería aparecer en la pantalla.
Documentos oficiales de React

Para la aplicación que estamos creando, comenzaremos con un componente básico que muestre todos los productos devueltos por el servidor. Vamos a nombrarlo Main component. El componente debería encargarse de las siguientes cosas inicialmente:

  • Obtener todos los productos de la API (GET/api /products).
  • Almacenar los datos del producto en su estado.
  • Mostrar los datos del producto.

React no es un framework completo y, por lo tanto, la biblioteca no tiene características AJAX por sí misma. Utilizaré fetch(), la cual es una API estándar de JavaScript para recuperar los datos del servidor. Pero hay muchas alternativas para hacer llamadas AJAX al servidor.

resources/assets/js/component/Main.js

1
import React, { Component } from 'react';
2
import ReactDOM from 'react-dom';
3
4
/* Main Component */
5
class Main extends Component {
6
7
  constructor() {
8
  
9
    super();
10
    //Initialize the state in the constructor

11
    this.state = {
12
        products: [],
13
    }
14
  }
15
  /*componentDidMount() is a lifecycle method

16
   * that gets called after the component is rendered

17
   */
18
  componentDidMount() {
19
    /* fetch API in action */
20
    fetch('/api/products')
21
        .then(response => {
22
            return response.json();
23
        })
24
        .then(products => {
25
            //Fetched product is stored in the state

26
            this.setState({ products });
27
        });
28
  }
29
30
 renderProducts() {
31
    return this.state.products.map(product => {
32
        return (
33
            /* When using list you need to specify a key

34
             * attribute that is unique for each list item

35
            */
36
            <li key={product.id} >
37
                { product.title } 
38
            </li>      

39
        );
40
    })
41
  }
42
  
43
  render() {
44
   /* Some css code has been removed for brevity */
45
    return (
46
        <div>
47
              <ul>
48
                { this.renderProducts() }
49
              </ul> 

50
            </div> 

51
      
52
    );
53
  }
54
}

Aquí estamos inicializando el estado de los productos en una matriz vacía en el constructor. Una vez que el componente se monta, usamos fetch() para recuperar los productos de /api/products y almacenarlos en el estado. El método de renderizado se usa para describir la interfaz de usuario del componente. Todos los productos se muestran como una lista allí.

Screenshot of the React Application - List of all productsScreenshot of the React Application - List of all productsScreenshot of the React Application - List of all products

La página solo muestra los títulos de los productos, lo cual es aburrido. Además, aún no tenemos elementos interactivos. Hagamos clic en el título del producto y, al hacer clic, se mostrarán más detalles sobre el producto.

Visualización de los datos del producto

Aquí está la lista de cosas que debemos cubrir:

  • Un estado para rastrear el producto al que se le hizo clic. Llamémoslo currentProduct con un valor null inicial.
  • Cuando se hace clic en el título de un producto, este se actualiza. State.currentProduct.
  • Los detalles del producto afectado se muestran a la derecha. Hasta que se seleccione un producto, por defecto se mostrará el mensaje "Sin producto seleccionado".

resources/assets/js/component/Main.js

1
import React, { Component } from 'react';
2
import ReactDOM from 'react-dom';
3
4
/* Main Component */
5
class Main extends Component {
6
7
  constructor() {
8
  
9
    super();
10
11
    /* currentProduct keeps track of the product currently

12
     * displayed */
13
    this.state = {
14
        products: [],
15
        currentProduct: null
16
    }
17
  }
18
 
19
  componentDidMount() {
20
    //code omitted for brevity

21
  }
22
23
 renderProducts() {
24
    return this.state.products.map(product => {
25
        return (
26
            //this.handleClick() method is invoked onClick.

27
            <li onClick={
28
                () =>this.handleClick(product)} key={product.id} >
29
                { product.title } 
30
            </li>      

31
        );
32
    })
33
  }
34
  
35
   handleClick(product) {
36
    //handleClick is used to set the state

37
    this.setState({currentProduct:product});
38
  
39
  }
40
  
41
  render() {
42
   /* Some css code has been removed for brevity */
43
    return (
44
        <div>
45
              <ul>
46
                { this.renderProducts() }
47
              </ul> 

48
            </div> 

49
      
50
    );
51
  }
52
}

Aquí agregamos createProduct en el estado y lo inicializamos con el valor null. La línea onClick = {() =>this.handleClick(product)} invoca el método handleClick() cuando se hace clic en el elemento de la lista. El método handleClick() actualiza el estado de currentProduct.

Ahora, para mostrar los datos del producto, podemos representarlo dentro del componente Main o crear un nuevo componente. Como se mencionó anteriormente, dividir la interfaz de usuario en componentes más pequeños es la forma React de hacer las cosas. Por lo tanto, crearemos un nuevo componente y lo nombraremos Product.

El componente Product está anidado dentro del componente Main. El componente principal pasa su estado como accesorios. El componente de producto acepta estos accesorios como entrada y representa la información relevante.

resources/assets/js/component/Main.js

1
render() {
2
  return (
3
    /* The extra divs are for the css styles */
4
        <div>
5
            <div>
6
             <h3> All products </h3>

7
              <ul>
8
                { this.renderProducts() }
9
              </ul> 

10
            </div> 

11
          
12
            <Product product={this.state.currentProduct} />

13
        </div>

14
    );
15
  }
16
}

resources/assets/js/component/Product.js

1
import React, { Component } from 'react';
2
3
/* Stateless component or pure component

4
 * { product } syntax is the object destructing

5
 */
6
const Product = ({product}) => {
7
   
8
  const divStyle = {
9
      /*code omitted for brevity */
10
  }
11
12
  //if the props product is null, return Product doesn't exist

13
  if(!product) {
14
    return(<div style={divStyle}>  Product Doesnt exist </div>);

15
  }
16
    
17
  //Else, display the product data

18
  return(  
19
    <div style={divStyle}> 
20
      <h2> {product.title} </h2>

21
      <p> {product.description} </p>

22
      <h3> Status {product.availability ? 'Available' : 'Out of stock'} </h3>

23
      <h3> Price : {product.price} </h3>

24
     
25
    </div>

26
  )
27
}
28
29
export default Product ;

La aplicación debería verse más o menos así ahora:

Screenshot of the React application with product details displayedScreenshot of the React application with product details displayedScreenshot of the React application with product details displayed

Agregar un nuevo producto

Hemos implementado con éxito el diseño correspondiente a la recuperación de todos los productos y su visualización. A continuación, necesitamos un formulario para agregar un nuevo producto a la lista de productos. El proceso para agregar un producto puede parecer un poco más complejo que simplemente obtener los datos de una API.

Esto es lo que creo que se requiere para desarrollar esta función:

  • Un nuevo componente con estado que representa la interfaz de usuario para un formulario de entrada. El estado del componente contiene los datos del formulario.
  • En el envío, el componente secundario pasa el estado al componente Main usando una devolución de llamada.
  • El componente Main tiene un método, digamos handleNewProduct(), que maneja la lógica para iniciar una solicitud POST. Al recibir la respuesta, el componente Main actualiza su estado (tanto this.state.products como this.state.currentProduct)

Eso no suena muy complejo, ¿verdad? Vamos a hacerlo paso a paso. Primero, crea un nuevo componente. Voy a llamarlo AddProduct.

resources/assets/js/component/AddProduct.js

1
class AddProduct extends Component {
2
3
  constructor(props) {
4
    super(props);
5
       /* Initialize the state. */
6
       this.state = {
7
          newProduct: {
8
              title: '',
9
              description: '',
10
              price: 0,
11
              availability: 0
12
          }
13
        }
14
    
15
    //Boilerplate code for binding methods with `this`

16
    this.handleSubmit = this.handleSubmit.bind(this);
17
    this.handleInput = this.handleInput.bind(this);
18
  }
19
  
20
  /* This method dynamically accepts inputs and stores it in the state */
21
  handleInput(key, e) {
22
    
23
    /*Duplicating and updating the state */
24
    var state = Object.assign({}, this.state.newProduct); 
25
    state[key] = e.target.value;
26
    this.setState({newProduct: state });
27
  }
28
 /* This method is invoked when submit button is pressed */
29
  handleSubmit(e) {
30
    //preventDefault prevents page reload   

31
    e.preventDefault();
32
    /*A call back to the onAdd props. The current

33
     *state is passed as a param

34
     */
35
    this.props.onAdd(this.state.newProduct);
36
  }
37
38
  render() {
39
    const divStyle = {
40
        /*Code omitted for brevity */ }
41
    
42
    return(
43
      <div> 
44
        <h2> Add new product </h2>

45
        <div style={divStyle}> 
46
        /*when Submit button is pressed, the control is passed to 

47
         *handleSubmit method 

48
         */
49
        <form onSubmit={this.handleSubmit}>
50
          <label> Title: 
51
           { /*On every keystroke, the handeInput method is invoked */ }
52
            <input type="text" onChange={(e)=>this.handleInput('title',e)} />

53
          </label>

54
          
55
          <label> Description: 
56
            <input type="text" onChange={(e)=>this.handleInput('description',e)} />

57
          </label>

58
          
59
         { /* Input fields for Price and availability omitted for brevity */}
60
61
          <input type="submit" value="Submit" />
62
        </form>

63
      </div>

64
    </div>)

65
  }
66
}
67
68
export default AddProduct;
69
  

El componente básicamente representa un formulario de entrada, y todos los valores de entrada se almacenan en el estado (this.state.newProduct). Luego, al enviar el formulario, se invoca el método handleSubmit(). Pero AddProduct necesita comunicar la información a los padres, y lo hacemos mediante una devolución de llamada.

El componente Main, que es el padre, pasa una referencia de función como accesorios. El componente secundario, AddProduct en nuestro caso, invoca este accesorio para notificar al padre del cambio de estado. Entonces la línea this.props.onAdd (this.state.newProduct); es un ejemplo de una devolución de llamada que notifica al componente padre del nuevo producto.

Ahora, dentro del componente Main, declararemos <AddProduct /> de la siguiente manera:

1
<AddProduct onAdd={this.handleAddProduct} /> 

El controlador de eventos onAdd está encadenado al método handleAddProduct() del componente. Este método aloja el código para realizar una solicitud POST al servidor. Si la respuesta indica que el producto se ha creado correctamente, se actualiza el estado de productscurrentProducts.

1
 handleAddProduct(product) {
2
     
3
    product.price = Number(product.price);
4
    /*Fetch API for post request */
5
    fetch( 'api/products/', {
6
        method:'post',
7
        /* headers are important*/
8
        headers: {
9
          'Accept': 'application/json',
10
          'Content-Type': 'application/json'
11
        },
12
        
13
        body: JSON.stringify(product)
14
    })
15
    .then(response => {
16
        return response.json();
17
    })
18
    .then( data => {
19
        //update the state of products and currentProduct

20
        this.setState((prevState)=> ({
21
            products: prevState.products.concat(data),
22
            currentProduct : data
23
        }))
24
    })
25
26
  }  

No olvides vincular el método handleProduct a la clase utilizando this.handleAddProduct = this.handleAddProduct.bind (this); en el constructor. Y aquí está la versión final de la aplicación:

Screenshot of the final version of the applicationScreenshot of the final version of the applicationScreenshot of the final version of the application

¿Qué sigue?

La aplicación está incompleta sin las funciones de eliminar y actualizar. Pero si has seguido de cerca el tutorial hasta ahora, deberías poder llenar el vacío sin muchos problemas. Para comenzar, te proporcionaré la lógica del controlador de eventos para el escenario de eliminación y actualización.

Lógica para eliminar un producto

1
  handleDelete() {
2
     
3
    const currentProduct = this.state.currentProduct;
4
    fetch( 'api/products/' + this.state.currentProduct.id, 
5
        { method: 'delete' })
6
        .then(response => {
7
          /* Duplicate the array and filter out the item to be deleted */
8
          var array = this.state.products.filter(function(item) {
9
          return item !== currentProduct
10
        });
11
     
12
        this.setState({ products: array, currentProduct: null});
13
14
    });
15
  }

Lógica para actualizar un producto existente

1
handleUpdate(product) {
2
3
    const currentProduct = this.state.currentProduct;
4
    fetch( 'api/products/' + currentProduct.id, {
5
        method:'put',
6
        headers: {
7
          'Accept': 'application/json',
8
          'Content-Type': 'application/json'
9
        },
10
        body: JSON.stringify(product)
11
    })
12
    .then(response => {
13
        return response.json();
14
    })
15
    .then( data => {
16
        /* Updating the state */
17
        var array = this.state.products.filter(function(item) {
18
          return item !== currentProduct
19
      })
20
        this.setState((prevState)=> ({
21
            products: array.concat(product),
22
            currentProduct : product
23
        }))
24
    }) 
25
  }

Lo que debes hacer es sumergirte, ensuciarte las manos y finalizar la aplicación usando la lógica anterior. Te daré una pista: el botón de eliminación idealmente debe ir dentro del componente Product, mientras que la función de actualización debe tener un componente propio. Los animo a que acepten este desafío y terminen los componentes faltantes.

Resumen

Hemos recorrido un largo camino desde donde comenzamos. Primero, creamos una API REST usando el framework Laravel. Luego, discutimos nuestras opciones para mezclar Laravel y React. Finalmente, construimos una interfaz para la API usando React.

Aunque nos centramos principalmente en crear una aplicación de una sola página con React, puedes crear widgets o componentes que están montados en elementos específicos en tus vistas. React es muy flexible porque es una biblioteca y una buena.

Durante el último par de años, React ha crecido en popularidad. De hecho, tenemos una serie de artículos en el mercado que están disponibles para su compra, revisión, implementación, etc. Si buscas recursos adicionales en React, no dudes en consultarlos.

¿Has intentado experimentar con Laravel y React antes? ¿Qué piensas? Comparte tu experiencia con nosotros en los comentarios.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.