VAADIN

Nuestros primeros Vaadin add-ons integrando React

15 agosto, 2024
Introducing our first Vaadin add-ons integrating React Components

Vaadin 24.4 incluye nuevas y emocionantes capacidades para integrar React en aplicaciones Vaadin de varias formas. Esto ofrece a los desarrolladores una gama de opciones de integración, proporcionando una flexibilidad sin precedentes para construir aplicaciones web modernas. Puedes explorar las diversas formas de integración y las razones detrás de esta mejora en este post del blog de Vaadin.

Como ya todos saben, nos encanta crear add-ons para Vaadin, por lo que nos emocionó explorar la posibilidad de crear nuevos add-ons que integren componentes de React. Creemos que esto abre un mundo completamente nuevo de posibilidades para las aplicaciones Vaadin.

Los Primeros Pasos

Después del release de Vaadin 24.4, nos sumergimos en la documentación para entender cómo integrar componentes de React con Vaadin Flow.

Seleccionamos algunos componentes de React que parecían interesantes y no tan complejos para tratarse de un primer intento, y así comenzó nuestra aventura.

Nuestra primera elección fue un componente de resaltado de sintaxis, y el segundo fue un componente que permite recortar imágenes:

  • Syntax Highlighter: Este componente permite mostrar fragmentos de código formateados con diferentes colores en varios lenguajes de programación, mejorando la legibilidad y presentación. Es fácil de implementar y soporta múltiples lenguajes y temas.
  • Image Crop: Este componente permite a los usuarios recortar imágenes ofreciendo numerosas propiedades para ajustar el proceso de recorte de acuerdo a necesidades particulares.

El Proceso

La documentación de Vaadin es bastante completa, así que resumamos los puntos clave para lograr esta integración. Hay dos partes principales a considerar:

1 – Crear un Componente Web Adaptador del Lado del Cliente:

  • Crear un wrapper del componente React como un componente web custom extendiendo la clase ReactAdapterElement.
  • Implementar la función render para acceder al estado y eventos del componente utilizando argumentos de hooks.
  • Como es un componente web, para exportarlo simplemente usar customElements.define().

Por ejemplo, para integrar react-image-crop creamos un archivo image-crop.tsx:

import { ReactAdapterElement, RenderHooks } from 'Frontend/generated/flow/ReactAdapter';
import { JSXElementConstructor, ReactElement, useRef } from "react";
import React from 'react';
import { type Crop, ReactCrop, PixelCrop, makeAspectCrop, centerCrop } from "react-image-crop";

class ImageCropElement extends ReactAdapterElement {

	protected render(hooks: RenderHooks): ReactElement<any, string | JSXElementConstructor<any>> | null {

		const [crop, setCrop] = hooks.useState<Crop>("crop");
		const [imgSrc] = hooks.useState<string>("imgSrc");
		const imgRef = useRef<HTMLImageElement>(null);
		const [imgAlt] = hooks.useState<string>("imgAlt");
		const [aspect] = hooks.useState<number>("aspect");
		const [circularCrop] = hooks.useState<boolean>("circularCrop", false);
		const [keepSelection] = hooks.useState<boolean>("keepSelection", false);
		const [disabled] = hooks.useState<boolean>("disabled", false);
		const [locked] = hooks.useState<boolean>("locked", false);
		const [minWidth] = hooks.useState<number>("minWidth");
		const [minHeight] = hooks.useState<number>("minHeight");
		const [maxWidth] = hooks.useState<number>("maxWidth");
		const [maxHeight] = hooks.useState<number>("maxHeight");
		const [ruleOfThirds] = hooks.useState<boolean>("ruleOfThirds", false);

		....

		return (
			<ReactCrop
				crop={crop}
				onChange={(c: Crop) => onChange(c)}
				onComplete={(c: PixelCrop) => onComplete(c)}
				circularCrop={circularCrop}
				aspect={aspect}
				keepSelection={keepSelection}
				disabled={disabled}
				locked={locked}
				minWidth={minWidth}
				minHeight={minHeight}
				maxWidth={maxWidth}
				maxHeight={maxHeight}
				ruleOfThirds={ruleOfThirds}
			>
				<img
					ref={imgRef}
					src={imgSrc}
					alt={imgAlt}
					onLoad={onImageLoad} />
			</ReactCrop>
		);
	}
	
}

customElements.define("image-crop", ImageCropElement);

Igualmente, para react-syntax-highlighter creamos un archivo react-syntax-highlighter.tsx:

import {type ReactElement} from 'react';
import SyntaxHighlighter from 'react-syntax-highlighter';
import * as styles from 'react-syntax-highlighter/dist/esm/styles/hljs';
import {ReactAdapterElement, type RenderHooks} from "Frontend/generated/flow/ReactAdapter";
import React from 'react';

class SyntaxHighlighterElement extends ReactAdapterElement {

    protected override render(hooks: RenderHooks): ReactElement | null {
      const [language] = hooks.useState<string>("language"); 
      const [content] =  hooks.useState<string>("content");
      const [stylename] = hooks.useState<string>("stylename");
      const [showLineNumbers] = hooks.useState<boolean>("showLineNumbers");
      const [wrapLongLines] = hooks.useState<boolean>("wrapLongLines");
      
      return <SyntaxHighlighter 
      			language={language} 
      			style={styles[stylename]}
      			showLineNumbers={showLineNumbers}
	            wrapLongLines={wrapLongLines}
	            customStyle={{
	              width: "calc(100% - 16px)",
	              height: "calc(100% - 20px)"
	            }}
      			>
      			{content}
      		</SyntaxHighlighter>;
    }
}
  
customElements.define("syntax-highlighter",SyntaxHighlighterElement);

2 – Crear un Componente Adaptador del Lado del Servidor:

  • Crear una clase que extienda ReactAdapterComponent y que use el componente web creado en el punto 1.
  • Usar @NpmPackage para instalar el componente React elegido, y luego definir las anotaciones @Tag y @JsModule para referenciar el componente web.
  • Para sincronizar los estados entre el lado del servidor y el componente web adaptador, utilizar el método setState para enviar el estado desde el servidor y el método getState para recuperar el valor actual desde el cliente.

Puedes ver un ejemplo en la implementación de la clase ImageCrop.java:

@NpmPackage(value = "react-image-crop", version = "11.0.6")
@JsModule("./src/image-crop.tsx")
@Tag("image-crop")
@CssImport("react-image-crop/dist/ReactCrop.css")
public class ImageCrop extends ReactAdapterComponent {

  ....
  
  /**
   * Sets the source of the image to be cropped.
   *
   * @param imageSrc the image source
   */
  public void setImageSrc(String imageSrc) {
    setState("imgSrc", imageSrc);
  }

  /**
   * Gets the source of the image being cropped.
   *
   * @return the image source
   */
  public String getImageSrc() {
    return getState("imgSrc", String.class);
  }

  /**
   * Defines the crop dimensions.
   * 
   * @param crop the crop dimensions
   */
  public void setCrop(Crop crop) {
    setState("crop", crop);
  }

  /**
   * Gets the crop dimensions.
   */
  public Crop getCrop() {
    return getState("crop", Crop.class);
  }

  /**
   * Sets the aspect ratio of the crop.
   * For example, 1 for a square or 16/9 for landscape.
   * 
   * @param aspect the aspect ratio of the crop
   */
  public void setAspect(double aspect) {
    setState("aspect", aspect);
  }

  /**
   * Gets the aspect ratio of the crop.
   *
   * @return the aspect ratio
   */
  public double getAspect() {
    return getState("aspect", Double.class);
  }

 ....

 }

Los Resultados

Dimos un paso significativo hacia adelante en la integración de componentes de React al crear dos nuevos add-ons:

Esperamos que estos add-ons sean útiles a la comunidad y que sirvan como guía para quien esté buscando integrar componentes de React.

Conclusión

Antes de terminar, me gustaría decir que esta fue una aventura divertida. ¡Probar cosas nuevas siempre es emocionante!, ¿verdad? Creemos que este es un paso positivo para Vaadin, ya que implica que escucha y atiende las necesidades de los desarrolladores.

¿Qué consejos podemos compartir para este tipo de integración?

  • Elige un componente React que se ajuste mejor a tus necesidades y que tenga buena documentación, muchos ejemplos y un mantenimiento regular.
  • Siempre lee la documentación de Vaadin antes de comenzar.
  • ¡Aprende a amar React si aún no lo haces!

Visita nuestra sección de código abierto para aprender más sobre todos nuestros add-ons para Vaadin.

Gracias por leernos ¡and keep the code flowing!

Paola De Bartolo
By Paola De Bartolo

Ingeniera en Sistemas. Java Developer. Entusiasta de Vaadin desde el momento en que escuché "puedes implementar todo el UI con Java". Parte del #FlowingCodeTeam desde 2017.

¡Únete a la conversación!
Profile Picture

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.