Shoe, de Hermes Rivera

Bienvenido de nuevo a nuestra guía introductoria a React, donde te enseñamos las bases para que entiendas cómo funciona esta tecnología y, así, puedas escribir mejor código y crear mejores interfaces de usuario. En la entrada anterior, hablamos de cómo podías crear componentes React de forma muy sencilla como simples funciones puras. El mantra que repetí una y otra vez era: «a un componente React, le entran un conjunto de propiedades (props) y genera el HTML». Y acabamos con una pregunta: «si un componente es una función así de sencilla, ¿cómo podemos hacer nada útil con él?

Pues bien, hoy hablaremos de la parte dinámica de React: es decir, cómo podemos hacer que un componente reaccione a los eventos del usuario.

Preparando el entorno de trabajo

Me he dado cuenta de que, en la entrada anterior, me centré bastante en la teoría y no montamos ningún ejemplo… Pues bien, vamos a cambiar esa dinámica y vamos a empezar montando el esqueleto del ejemplo con el que trabajaremos hoy.

Lo único que voy a suponer es que tienes un WordPress instalado en tu PC y que tienes node y npm disponibles para desarrollar. Si no fuera el caso, en esta entrada te explicaba cómo puedes usar Lando para crear una instalación de WordPress local y en la documentación de npm te explican cómo ponerlo todo a punto.

Vale, pues partiendo de la base que tienes todas las herramientas listas, lo único que tenemos que hacer para empezar es crear un pequeño plugin de WordPress donde meteremos nuestro código JavaScript con sus componentes React y demás. Para ello, crea una carpeta llamada react-example en wp-content/plugins y dentro mete el fichero react-example.php con el siguiente contenido:

<?php
/**
 * Plugin Name:  React Example
 * Description:  Example.
 * Version:      1.0.0
 */
namespace React_Example;
defined( 'ABSPATH' ) or die();
function add_page() {
  add_menu_page(
    'React Example',
    'React Example',
    'read',
    'react-example',
    function () {
      echo '<div id="react-example-wrapper"></div>';
    }
  );
}
add_action( 'admin_menu', __NAMESPACE__ . '\add_page' );
function enqueue_scripts() {
  $screen = get_current_screen();
  if ( 'toplevel_page_react-example' !== $screen->id ) {
    return;
  }
  $dir = untrailingslashit( plugin_dir_path( __FILE__ ) );
  $url = untrailingslashit( plugin_dir_url( __FILE__ ) );
  if ( ! file_exists( "{$dir}/build/index.asset.php" ) ) {
    return;
  }
  $asset = include "{$dir}/build/index.asset.php";
  wp_enqueue_script(
    'react-example',
    "{$url}/build/index.js",
    $asset['dependencies'],
    $asset['version'],
    true
  );
}
add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_scripts' );

donde simplemente añadimos una nueva página en el escritorio de WordPress llamada React Example y en la que encolamos el fichero JavaScript donde crearemos nuestros componentes. El resultado debería ser una página en blanco, de momento:

Tutorial React - Página en blanco
Ejemplo con la página vacía.

y eso es porque aún no hemos creado el JavaScript que hemos encolado. Tal y como vimos en la entrada sobre cómo añadir un botón a Gutenberg, para preparar el código JavaScript deberás ejecutar los siguientes comandos en tu terminal (en la carpeta del plugin que estamos creando):

npm init
npm install --save-dev @wordpress/scripts

siguiendo las instrucciones que aparecen en pantalla y acordándote de modificar package.json para que tenga los comandos con los que transpilar el código (insisto, lo tienes explicado con detalle aquí).

Finalmente, crea una carpeta src con un fichero index.js con el siguiente contenido:

// Import dependencies
import { render } from '@wordpress/element';
// Create component
const HelloWorld = () => <div>Hello World</div>;
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render( <HelloWorld />, wrapper );

Ejecuta npm run build (o npm run start si quieres que vaya transpilando el código con cada cambio que hagas en tus ficheros fuente), refresca la página y, voilà, ya tienes todo preparado:

Hola mundo en nuestro tutorial de React
El famoso ¡Hola, mundo! implementado con React.

Creación de un Componente funcional en React

Ahora que ya lo tenemos todo funcionando, podemos (por fin) crear un componente que reacciona a los eventos del usuario. El ejemplo que quiero que implementemos juntos es el siguiente:

Tutorial React - Contador
Nuestro componente es un pequeño contador con un par de botones para incrementar o decrementar su valor.

Un sencillo contador de clics que nos permite sumar 1 o quitar 1 según le demos al + o al -. Es muy sencillo pero te servirá para entender la diferencia entre el «modelo de datos», la «lógica de la aplicación» y la «interfaz de usuario».

Organizando el código un poco mejor

Vamos a empezar mejorando un poco la organización de nuestro código. Aunque el ejemplo de hoy, extremadamente sencillo, no lo requeriría, es una buena práctica acostumbrarse cuanto antes a hacerlo.

En la carpeta src vamos a crear una nueva carpeta donde meteremos todos nuestros componentes y que llamaremos components. Nuestra aplicación solo tiene un único componente, el Counter, así que vamos a crear el fichero counter.js dentro:

export function Counter() => (
  <p>Counter</p>
);

que lo único que hace es exportar la función encargada de representar nuestro componente. Luego, modificamos src/index.js para que, en lugar de pintar nuestro HelloWorld de ejemplo, importe el componente Counter y lo use:

// Import dependencies
import { render } from '@wordpress/element';
import { Counter } from './components/counter';
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render( <Counter />, wrapper );

y ya tienes el nuevo componente en tu interfaz.

Implementando el componente con el contador de clics

Vale, el objetivo de hoy es implementar un componente que nos permita contar cuantos clics se han hecho en un botón de sumar y otro de restar. Esto quiere decir que nuestro componente debe tener tres cosas:

  • Un elemento en el que mostrar el valor del contador
  • Un botón + para sumar
  • Un botón - para restar

Si te pones a programar el componente, probablemente te salga algo tal que así:

export const Counter = ( { value } ) => (
  <div>
    <div>Counter: <strong>{ value }</strong></div>
    <button>+</button>
    <button>-</button>
  </div>
);

y es que intuyes que tu contador debe recibir el valor del número de clics que llevamos acumulados como una prop de entrada y sabes que debe haber dos botones que modifiquen ese valor, pero no tienes ni idea de cómo hacerlo si un componente no puede modificar sus props… Así que, ¿cómo narices hacemos que esos botones hagan algo?

Funciones como parámetros y separación de responsabilidades

Repite conmigo: un componente React es una función que recibe propiedades y genera HTML. Esta frase que parece tan inocente encierra una propiedad muy potente: el que invoque esta función (o, en otras palabras, el que use este componente) debe darle todos los parámetros (propiedades) que espera para que funcione correctamente.

Tu intuición ya te decía que el número de clics que llevamos es algo que el componente «recibe», le viene dado. Esto quiere decir que se trata de una variable que no gestiona el propio componente, sino otro ente que aún no conocemos. Simplemente sabemos que, cuando «alguien» usa nuestro componente Counter, ese «alguien» nos dará el valor exacto que quiere que mostremos en el contador.

Pero también sabemos que nuestro componente Counter debe poder modificar el valor de esa variable. O, en otras palabras, debe de poder ejecutar una acción cuando el usuario hace clic en + y otra acción diferente cuando hace clic en -.

Si hilas estas dos ideas te darás cuenta de la solución: el componente Counter no solo espera recibir el valor (de tipo número) con el número de clics que llevamos, sino que también necesita dos propiedades adicionales (de tipo función) cuya ejecución modifique el valor en cuestión.

Es decir, el «contrato» de nuestro componente Counter (o, en otras palabras, las props que espera) es el siguiente:

  • value: es de tipo número y es el valor que aparece en el contador
  • onIncrease: es de tipo función y asume que, cuando se ejecute, sumará 1 al valor
  • onDecrease: también es de tipo función y asume que, cuando se ejecute, restará 1 al valor

Si supiéramos que a nuestro componente le van a dar estas tres props, entonces escribir el código es muy sencillo:

export const Counter = ( { value, onIncrease, onDecrease } ) => (
  <div>
    <div>Counter: <strong>{ value }</strong></div>
    <button onClick={ onIncrease }>+</button>
    <button onClick={ onDecrease }>-</button>
  </div>
);

Fíjate que nuestro componente no está ejecutando ninguna de las funciones que recibe como parámetro, porque ya sabes que una función pura no puede tener efectos secundarios (y ejecutar una de esas funciones para modificar value sería un efecto secundario).

Lo único que hace es generar el HTML que nos interesa y «conectar» un posible evento futuro de clic en alguno de los botones button con el callback correspondiente. Se trata de una función pura porque dados las mismas tres propiedades (el mismo valor y las mismas funciones de actualización), el resultado siempre, siempre, siempre es el mismo HTML con las mismas «conexiones».

Por desgracia, si intentas usar el componente, verás que no pinta ningún valor y los botones no hacen absolutamente nada 🙁

Pasándole al componente TODAS las propiedades que necesita

Imagino que ya sabes por qué el componente no está pintando nada y los botones no hacen nada. Cuando usamos un componente, debemos cumplir con su contrato y darle todas las props que espera. Pero nosotros, en nuestro fichero JavaScript principal, tenemos esto:

render( <Counter />, wrapper );

Vamos, que ni le estamos pasando un valor, ni le estamos pasando una función para sumar 1 a ese valor, ni le estamos pasando una función para restarle 1. ¡Y nos sorprendemos cuando el tema no funciona!

Pues bien, vamos a solucionarlo rápidamente definiendo una variable que almacene el valor actual de nuestro contador y un par de funciones para modificar dicho valor. Por comodidad, vamos a hacer todo esto en el fichero index.js:

// Import dependencies
import { render } from '@wordpress/element';
import { Counter } from './components/counter';
// Store value
let value = 0;
function setValue( newValue ) {
  value = newValue;
}
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
render(
  <Counter
    value={ value }
    onIncrease={ () => setValue( value + 1 ) }
    onDecrease={ () => setValue( value - 1 ) }
  />,
  wrapper
);

Gracias a esta arquitectura, conseguimos separar totalmente la interfaz de usuario (lo que es el componente) de la gestión de los datos, ya que los datos los tenemos (de un modo un poco chapuza, ahora mismo) como una variable en nuestro fichero index.js y el componente es totalmente agnóstico a la forma en que se guardan y/o modifican.

Si volvemos a refrescar la página, verás que ahora el contador sí muestra el valor por defecto con el que empezamos (un 0), pero si le damos a los botones + y - las cosas siguen sin funcionar…

Pintando de nuevo el componente cuando se produce un cambio

Te propongo un pequeño cambio. Vamos a modificar la funcion setValue para que, además de asignar el nuevo valor a value, también lo pinte por consola:

function setValue( newValue ) {
  value = newValue;
  console.log( 'Value is', value );
}

y vuelvas a comprobar si tu componente funciona o no. Para ello, abre la consola de desarrolladores en tu navegador y mira qué muestra el registro JavaScript después de darle varias veces al botón +:

Ejemplo de React en el que el componente no se actualiza
El contador no se actualiza en la interfaz de usuario, pero aparentemente debería, porque en la consola sí se ve funcionar…

¡Vaya! Parece que el valor de la variable value sí se va actualizando con cada clic que realizamos en los botones de nuestro componente. Entonces, ¿por qué el componente no muestra el nuevo valor?

Muy sencillo: porque pintar un componente consiste en invocar su función, a la que le pasamos un conjunto de props y ella nos devuelve el HTML. Si solo invocamos la función una vez, el componente que queda en el DOM es el resultado de esa primera invocación. En nuestro caso, nos queda el componente tal cual se generó cuando la variable value era 0.

Si queremos que el componente se actualice cuando se produce un cambio en value, tenemos que especificarlo a mano (en index.js):

// Import dependencies
import { render } from '@wordpress/element';
import { Counter } from './components/counter';
// Store value
let value = 0;
function setValue( newValue ) {
  value = newValue;
  refreshComponent();
}
// Render component in DOM
const wrapper = document.getElementById( 'react-example-wrapper' );
function refreshComponent() {
  render(
    <Counter
      value={ value }
      onIncrease={ () => setValue( value + 1 ) }
      onDecrease={ () => setValue( value - 1 ) }
    />,
    wrapper
  );
}
refreshComponent();

en la que creamos una función refreshComponent que usamos para pintar el componente por primera vez así como cada vez que invocamos a setValue. Después de este cambio, por fin, todo funciona como esperamos:

Tutorial React - Contador
Componente funcionando correctamente.

¿En serio el renderizado de componentes es tan cutre?

Si has llegado hasta aquí siguiendo el tutorial de hoy, probablemente hayas quedado un poco decepcionado con la forma en la que re-pintamos los componentes React. Y es normal, porque realmente la parte final que hemos hecho es una chapuza. Pero una chapuza que nos ayudará a entender mejor lo que pasa entre bastidores.

Hoy has aprendido dos lecciones muy importantes:

  1. Las propiedades que puede recibir un componente no son únicamente «datos» (como un número, un string, un array, un objeto…) sino que también pueden ser «funciones». Podemos conectar estas funciones al mecanismo de eventos del DOM para conseguir que se ejecuten «automágicamente» cuando el usuario haga algo que lance un evento del DOM (como hacer clic en un elemento, mover el cursor, darle al teclado, cambiar el valor de un campo de texto, etc).
  2. Los componentes (UI) y los datos que usan son totalmente independientes los unos de los otros. A un componente React le da igual quién y cómo almacene los datos: lo único que le importa es que le pasen todas las props que necesita para funcionar.

En la próxima entrada daremos otra vuelta de tuerca a la integración de componentes y datos. En concreto, te explicaré cómo podemos usar los almacenes de WordPress (tecnología Redux) para optimizar el almacenamiento del estado de nuestra aplicación así como el proceso de re-pintado de componentes. ¡No te la pierdas!

Imagen destacada de Hermes Rivera en Unsplash.

2 respuestas a «Introducción a React (parte 2)»

  1. Avatar de Wajari Velásquez
    Wajari Velásquez

    Me está encantando esta serie de entradas de introducción a React. Explicaciones claras y didácticas. ¡Muchas gracias! 🙂

    Ya con ganas de la próxima entrega.

    Un abrazo,

    1. Avatar de David Aguilera

      Muchas gracias, Wajari. Esa es precisamente la idea, centrarnos en un único concepto en cada entrada e ir mejorando paso a paso 🙂

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

He leído y acepto la política de privacidad de Nelio Software

Tus datos personales se almacenarán en SiteGround y serán usados por Nelio Software con el único objetivo de publicar tu comentario aquí. Con el envío de este comentario, nos das el consentimiento expreso para ello. Escríbenos para acceder, rectificar, limitar o eliminar tus datos personales.