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



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 react
, react-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:



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í.



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 valornull
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:



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 (tantothis.state.products
comothis.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 products
y currentProducts
.
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:



¿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.