En este capítulo os voy a enseñar a utilizar React dentro de Qwik utilizando Qwik React
basándonos en los conocimientos iniciales que hemos adquirido sobre las integraciones que nos proporciona Qwik.
La ventaja principal de utilizar Qwik React
es que vamos a poder utilizar componentes y bibliotecas existentes de React dentro de Qwik, tanto de nuestros proyectos o paquetes NPM que podamos encontrar.
Con esto estaremos dando posibilidad a que podamos migrar a Qwik proyectos React con cierta facilidad.
Obviamente todo depende de la magnitud y complejidad del proyecto aunque con estas claves estaremos más cerca de poder hacerlo.
Esto nos va a permitir aprovechar el gran ecosistema de componentes y bibliotecas de React, como podrían ser entre muchas más opciones:
También podemos aprovecharnos de obtener los beneficios que nos proporciona Qwik sin tener que reescribir completamente nuestra aplicación de React, que esto es un plus por si queremos adaptar un proyecto React a Qwik.
Siguendo todos los pasos que se van a mencionar a continuación conseguiremos aprender las bases para poder usar componentes React de proyectos propios o de librerías de componentes propias o de terceros.
Integrando React en Qwik: Instalación y configuración.
qwikify$
— Convierte componentes React en Qwik.
Primer ejemplo Básico — Componente de saludo.
Componente React usando props.
Hidratando un componente React.
Hidratando más de un componente React.
Usar librería React publicada en NPM - I — react-day-picker
.
Usar librería React publicada en NPM - II — react-modern-calendar-datepicker
.
Al igual que la mayoría de las bibliotecas o frameworks de Frontend de JavaScript, React está utilizando la hidratación, lo que tiene sus desventajas y puede ser más costoso su uso de lo que se podría pensar.
Qwik no necesita hidratación al cargar. Es solo HTML puro. Esto hace que Qwik sea más rápido para cargar las interfaces de usuario, con las cuales los usuarios pueden comenzar a interactuar.
Esto es algo a tener muy en cuenta al considerar incluir componentes de React en tus aplicaciones Qwik.
Acerca de la hidratación
En el primer capítulo ya os he mostrado las características principales de Qwik haciendo una comparativa con tecnologías Frontend como Angular, React y Vue. En el caso de necesidad de refrescar conceptos, os invito a que volváis a leer este capítulo.
Qwik React es una herramienta que nos va a permitir utilizar componentes de React en Qwik, incluyendo todo el ecosistema de paquetes de componentes como Material UI, Threejs y React Spring entre otras muchas opciones como se ha mencionado al inicio del capítulo.
Preparativos
Antes de comenzar con la integración debemos de tener el proyecto de Qwik creado y listo para iniciarlo con lo básico asignándole el nombre 20-integrations-react.
Esto ya lo sabemos hacer y si no, os invito a mirar el primer capítulo mencionado.
Hay dos formas de integrar React en Qwik:
Manual
: Completando todos los pasos y configuraciones necasarias que os voy a exponer a continuación.
Automática
: Mediante un comando ejecutado y varias selecciones te realiza todas las configuraciones y te añade varios paquetes de componentes como @emotion/react
, @emotion/styled
, @mui/material
y @mui/x-data-grid
.
En este capítulo vamos a aprender realizarlo todo de manera manual, para no instalar esas dependencias ni generar los ejemplos que viene por defecto a la hora de hacerlo de manera automática y a su vez podemos entender todo el proceso paso a paso.
Si quisieráis hacerlo de manera automática añadiendo esas dependencias y ejemplos, solo tenéis que ejecutar el siguiente comando y seguir las instrucciones:
npm run qwik add react
Comenzamos con la integración de React en Qwik, lo primero que vamos a realizar es instalar las dependencias necesarias de React y el plugin @builder.io/qwik-react
mediante el siguiente comando (pongo las versiones para trabajar con la versión actual (01/2024) de Qwik, esto en el futuro cambiará, pero lo que es el procedimiento no) :
npm install @builder.io/qwik-react@0.5.0 @types/react@18.2.21 @types/react-dom@18.2.7 react@18.2.0 react-dom@18.2.0 --force
Se instalarán las dependencias y se registrará esta información en el package.json
.
Tenéis que tener en cuenta que esto NO ES UNA EMULACIÓN de React, si no que estará usando la librería actual de React.
Ahora vamos al fichero vite.config.ts
y pasamos de esto:
import { defineConfig } from 'vite';
import { qwikVite } from '@builder.io/qwik/optimizer';
import { qwikCity } from '@builder.io/qwik-city/vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig(() => {
return {
plugins: [qwikCity(), qwikVite(), tsconfigPaths()],
preview: {
headers: {
'Cache-Control': 'public, max-age=600',
},
},
};
});
A añadir el plugin Vite para integrar React en nuestra app:
import { defineConfig } from 'vite';
import { qwikVite } from '@builder.io/qwik/optimizer';
import { qwikCity } from '@builder.io/qwik-city/vite';
// 1.- importamos el plugin
import { qwikReact } from '@builder.io/qwik-react/vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig(() => {
return {
plugins: [
qwikCity(), qwikVite(), tsconfigPaths(),
// 2.- Lo añadimos
qwikReact()
],
preview: {
headers: {
'Cache-Control': 'public, max-age=600',
},
},
};
});
Llegados a este punto, ya tenemos la instalación y configuración inicial del plugin Qwik-React
añadido y lo que tenemos que hacer es pasar al siguiente punto donde vamos a crear un componente en React super sencillo, integrándolo en Qwik fácilmente usando la función qwikify$
después de entender que es y como es su funcionamiento.
Es la función que se usa en el plugin Qwik React para usar componentes existentes de React y envolverlos en esta función qwikify$
.
Su funcionamiento es bien sencillo. qwikify$
creará un componente Qwik que se puede utilizar dentro de Qwik y que convertirá el componente de React en una isla que te permitirá la libertad de ajustar cuando el componente de React debe hidratar.
La función qwikify$
es exportada de @builder.io/qwik-react
y convierte los componentes de React en componentes de Qwik que se pueden utilizar en toda la aplicación, lo que va a permitir que Qwik implemente la hidratación parcial de los componentes de React.
No se pueden usar componentes de React en Qwik sin convertirlos primero para eliminar el patrón de hidratación de React, que NO ES COMPATIBLE con Qwik.
Recordadlo, para convertirlo a Qwik usamos la función qwikify$()
.
Otra cosa importante a recordar es asegurarse de que el archivo que contiene su componente de React tenga este comentario:
/** @jsxImportSource react */
Sabiendo estos detalles, vamos a la acción, a implementarlo con diferentes casos de uso reales y como siempre, vamos a empezar por una funcionalidad muy básica, que en este caso será crear un componente React básico con un saludo inicial como:
Hello From React
Siguiendo lo anterior, plasmamos lo aprendido en este punto donde empezamos con un componente React llamado react.tsx
dentro de src/integrations/react/hello
junto con el comentario al inicio del fichero con el tag de @jsxImportSource react
, que es MUY MUY importante. Sin en el, no podremos usarlo:
/** @jsxImportSource react */
// Creación del componente React de manera estándar
export function HelloReact() {
return <div>Hello from React</div>;
}
Estos componentes los vamos a añadir en src/integrations/react
con el objetivo de tenerlos más a mano y más organizados. Crear el directorio siguiendo esa ruta y seguimos.
Y para convertirlo a Qwik, debemos de añadir el uso de la función qwikify$()
trayendo el componente del fichero react.tsx
. Esto lo plasmamos así:
import { qwikify$ } from '@builder.io/qwik-react';
// 1.- Usamos en el componente de react creado
import { HelloReact } from './react';
// 2.- Convertimos el componente React en un componente Qwik
export const QHelloReact = qwikify$(HelloReact);
A los componentes que convertimos a Qwik se recomienda nombrarlos en base a estas dos características:
Siendo nuestro componente a convertir HelloReact
el nombre que le daremos será Q
+ HelloReact
= QHelloReact
.
Ahora vamos a src/routes/index.tsx
y lo añadimos después de eliminar todo el contenido actual junto con el saludo ya desde el propio proyecto de Qwik:
import { component$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
// Importamos el componente de React con el saludo adaptado a Qwik
import { QHelloReact } from "~/integrations/react/hello";
export default component$(() => {
return (
<div class="container center">
<h2>Saludo con React</h2>
<QHelloReact />
<h2>Saludo con Qwik</h2>
Hello from Qwik
</div>
);
});
export const head: DocumentHead = {
...
};
Y este sería el resultado, como se puede apreciar, funciona sin ningún problema:
El paquete @builder.io/qwik-react
exporta la función qwikify$()
que nos permite convertir componentes de React en componentes de Qwik que podemos usar en toda nuestra aplicación.
Recordatorio
NO PODEMOS usar componentes de React en Qwik sin antes convertirlos utilizando
qwikify$()
. Aunque los componentes de React y Qwik se ven similares, son fundamentalmente muy diferentes.
Los componentes de React y Qwik no deben mezclarse en el mismo archivo.
Por esa razón hemos añadido el directorio src/integrations/react
para poder albergar esos componentes React y así usarlos.
Os recomiendo que coloquéis vuestros componentes de React en esa ubicación para facilitar su gestión y para acostumbrarnos a trabajar de una forma más ordenada.
Ya hemos hecho el ejemplo más básico, pasamos a otro ejemplo muy parecido a este, pero con un componente que tiene props.
Vamos al apartado donde estamos añadiendo los componentes de React y creamos un nuevo directorio dentro de src/integrations/react
que llamaremos custom-hello
.
Dentro de este nuevo directorio crearemos dos ficheros como antes.
Los ficheros serán react.tsx
donde definimos el componente de React y el fichero index.tsx
donde vamos a coger el componente React para convertirlo en un componente Qwik para poder usarlo con las características de este framework.
Añadimos el componente con el saludo personalizado mediante el uso de props
en el fichero react.tsx
:
/** @jsxImportSource react */
export function HelloCustomReact(props: {name: string}) {
return <div>Hello {props.name} from React</div>;
}
Y ahora realizamos la conversión en el fichero index.tsx
:
import { qwikify$ } from '@builder.io/qwik-react';
import {HelloCustomReact} from './react';
// Convertimos el componente React en un componente Qwik
export const QHelloCustomReact = qwikify$(HelloCustomReact);
Ahora para usarlo sería lo mismo como se ha hecho con el componente básico, pero tendremos que añadirle el prop
name el que se ha definido en el componente original de React.
Antes de nada en este fichero vamos a añadirle un componente aparte dentro src/routes/index.tsx
que recoge el prop
name como el de React, pero en este caso un componente Inline de Qwik que será lo siguiente:
export const HelloCustomQwik = (props: { name: string }) => (
<div>Hello {props.name} from Qwik</div>
);
Y teniendo esto y el componente de React convertido a Qwik, el fichero src/routes/index.tsx
se queda de la siguiente forma:
import { component$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
// Importamos el componente de React con el saludo adaptado a Qwik
import { QHelloCustomReact } from "~/integrations/react/custom-hello";
export const HelloCustomQwik = (props: { name: string }) => (
<div>Hello {props.name} from Qwik</div>
);
export default component$(() => {
return (
<div class="container center">
<h2>Saludo con React</h2>
<QHelloCustomReact name="Anartz" />
<h2>Saludo con Qwik</h2>
<HelloCustomQwik name="Anartz" />
</div>
);
});
export const head: DocumentHead = {
title: "Welcome to Qwik",
meta: [
{
name: "description",
content: "Qwik site description",
},
],
};
Si guardamos los cambios y vamos a la ruta principal, se podrá observar el siguiente resultado donde hemos pasado Anartz
mediante el prop
que hemos llamado name
:
Los ejemplos anteriores muestran cómo SSR el contenido estático de React en el servidor.
El beneficio de esto es que ese componente nunca se volverá a renderizar en el navegador y, por lo tanto, su código nunca se va a descargar al cliente.
Esto en principio, si es un componente que no cambia su estado, perfecto, pero, ¿Qué ocurriría si el componente debe ser interactivo y, por lo tanto, necesitamos descargar su comportamiento en el navegador?
Para ver un caso particular, vamos a trabajar con un componente, que es el típico contador que usamos para aprender a darle vida a un componente y a realizar cambios de estado dependiendo de nuestras acciones.
Comencemos con la construcción de un ejemplo simple en React.
Creamos el directorio llamado counter
con los dos ficheros que estamos usando por directorio, el correspondiente a la funcionalidad de React react.tsx
y al que llamamos index.tsx
donde lo convertimos en un componente de Qwik
src/integrations/react/counter/react.tsx
/** @jsxImportSource react */
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button className='react' onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Y ahora lo convertimos en un componente Qwik en el fichero src/integrations/react/counter/index.tsx
:
import { qwikify$ } from "@builder.io/qwik-react";
import { Counter } from "./react";
// Convertimos el componente React en un componente Qwik
export const QCounter = qwikify$(Counter);
Vamos al fichero index.tsx
correspondiente de la carpeta routes src/routes/index.tsx
y usamos ese componente retirando los anteriores utilizados del saludo personalizado:
import { component$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
import { QCounter } from "~/integrations/react/counter";
export default component$(() => {
return (
<div class="container center">
<h2>Contador</h2>
<QCounter />
</div>
);
});
export const head: DocumentHead = {
title: "Welcome to Qwik",
meta: [
{
name: "description",
content: "Qwik site description",
},
],
};
Guardamos los cambios y vamos a la ruta principal:
http://127.0.0.1:5173/
Esta será la apariencia que tendrá nuestro proyecto:
Ahora si hacemos click sobre el botón, debería de cambiar e ir aumentando el valor del contador.
Accionamos una, dos,…¡muchas veces! ¡No cambia! ¿Cuál es el motivo de que no funcione correctamente nuestro componente de React?
Un único motivo, que NO ESTÁ HIDRATADO y es algo que tenemos que hacer, hidratarlo.
Explicándolo con más detalles, esto se debe a que el componente de React no se ha descargado (lo que es la lógica de la funcionalidad) y, por lo tanto, el componente no se ha hidratado y eso hace que sea un componente muerto, sin vida.
Necesitamos decirle a Qwik que descargue el componente de React y lo hidrate, para que pueda cambiar el valor del contador al accionarlo.
Para poder realizar esto, debemos especificar las condiciones bajo las cuales queremos hacerlo mediante la propiedad eagerness
y el valor hover
.
En este caso, queremos descargar el componente cuando el usuario desplace el cursor sobre el botón (por el valor hover
) y lo hacemos mediante la siguiente opción en src/integrations/react/counter/index.tsx
:
...
// Especificamos eagerness para hidratar el componente cuando ponemos el cursor
// del ratón encima del botón.
export const QCounter = qwikify$(Counter, { eagerness: 'hover' });
Con esta opción tendremos otras tres situaciones en las que se hidrata, siendo las cuatro siguientes que están disponibles:
idle
: Ejecutará el código cuando se active la siguiente devolución de llamada inactiva (callback idle
).
load
: Ejecutará el código lo antes posible. Esto suele ser justo después del evento DOMContentLoaded
.
hover
: Al poner el cursor sobre el elemento del componente. Si no podemos el cursor sobre ese elemento, no se hidrata y no descargamos información que no estamos requiriendo.
visible
(opción por defecto): Ejecuta el efecto cuando el componente se vuelva visible
. Esta es una opción preferida porque retrasa la ejecución del efecto hasta que el componente sea visible en lugar de hacerlo con entusiasmo al iniciar la aplicación (estamos tratando de minimizar la cantidad de código que la aplicación ejecuta al iniciarse).
Ahora probando el ejemplo con ese ajuste podemos observar que haciendo click en el botón, el contador se incrementa ya que antes de hacer click hemos puesto sobre el botón el cursor y esto ha hecho que se hidratase (debido a la configuración eagerness: hover
) y así poder darle vida a este componente consiguiendo que funcione.
En la siguiente imagen se ve el estado del contador después de haber realizado la acción click hasta 9 veces:
Con estos ajustes, hemos conseguido que actué como un componente de Qwik y nos dé resultados como debe de ser.
Para ver lo que sucede en esta situación, os invito a que abráis la consola del navegador para ver el comportamiento de nuestra aplicación.
Lo que queremos ver es como se procesa el componente de React al poner el cursor del ratón sobre el botón del contador, que es lo que es el componente de React.
Estando en la consola, vamos a Red (Network)
(1), refrescamos la página lo primero y dejamos seleccionado únicamente los ficheros Javascript con extensión *.js
(2) y en el apartado de ficheros descargados
(3), son lo ficheros que se han descargado y que necesitamos en este momento.
Si no ponemos el cursor sobre el botón, como no necesitará hacer uso de el, no lo descargará.
Ahora pensamos en que si queremos descargarlo, lo primero, vamos a eliminar la información de los ficheros descargados
para tenerlo todo limpio (1) y como podemos observar en el apartado donde se visualizaban los ficheros
(2), ahora no tenemos nada registrado:
Vamos a forzar para que se descarguen los ficheros asociados al botón del contador.
Donde se puede observar que tenemos el fichero correspondiente al componente de React junto con el de la conversión a un componente de Qwik. Aparte de estos dos ficheros, se descargan otros ficheros necesarios que harán que todo funcione correctamente.
En este ejemplo, hemos abierto la consola para mostrar lo que sucede detrás de escena. Cuando pasamos el cursor sobre el botón
(1) , podemos ver
(2) que el componente React se procesa junto con otros ficheros necesarios
(3).
Los ficheros que hemos usado para crear el componente de Qwik desde el componente de React
podemos observarlos que se han cargado (4).
Si hacéis click sobre ellos de manera individual, podremos ver que es lo que se ha definido antes (prácticamente igual, se identifica bien)
En qcounter_qwikify
,...
En react.tsx
Esta situación se está dando ya que Qwik hidrata el componente al pasar el cursor del ratón por encima del botón.
Ahora que el componente está hidratado, puede interactuar con él y actualizará correctamente el valor del contador.
Al otorgar la propiedad eagerness
y el valor hover
en la función qwikify$()
, le estamos permitiendo ajustar las condiciones bajo las cuales se hidrata el componente, lo que afectará el rendimiento de inicio de su aplicación.
En el ejemplo anterior, teníamos una sola isla que retrasamos en la hidratación mediante el componente del contador.
En este caso particular, vamos a trabajar con dos componentes y de la forma que se va a hacer con dos, sería aplicable a más de dos componentes, por lo que entendiendo lo siguiente, vamos a poder trabajar perfectamente con proyectos más acordes a la realidad.
Al tener dos componentes tendremos múltiples islas y esto hará que haya una necesidad de comunicación entre ellas.
Este ejemplo muestra cómo hacer la comunicación entre islas con Qwik. Creamos un nuevo directorio en el apartado de integraciones src/integrations/react/button-display
.
Y en el apartado de la aplicación React añadimos en src/integrations/react/button-display/react.tsx
:
/** @jsxImportSource react */
export function Button({ onClick }: { onClick: () => void }) {
console.log('React <Button/> Render');
return <button onClick={onClick}>+1</button>;
}
export function Display({ count }: { count: number }) {
console.log('React <Display count=' + count + '/> Render');
return <p className='react'>Count: {count}</p>;
}
Y en el src/integrations/react/button-display/index.tsx
, lo convertimos a Qwik:
import { qwikify$ } from '@builder.io/qwik-react';
import { Button, Display } from './react';
export const QButton = qwikify$(Button, { eagerness: 'hover' });
export const QDisplay = qwikify$(Display);
Como se puede observar en el código, hemos aplicado la propiedad eagerness
únicamente en el componente <QButton />
, ya que va a ser el componente que va a interactuar y por lo tanto, el que debemos de hidratar para que funcione correctamente.
En cambio, <QDisplay />
, podríamos decir que es un componente "tonto", que solo va a recibir información desde los props
con el valor count
y con ese valor hará las actualizaciones correspondientes para pintar en la pantalla lo que le corresponde y nada más.
Eso si, estará unido al botón ya que esté se modificará en base a los cambios realizados por las acciones en <QButton />
Ahora teniendo esto, vamos a la ruta principal y sustituimos el componente <QCounter />
utilizado anteriormente para usar estos dos componentes y aparte añadimos un valor con useSignal
, para realizar control con el estado del valor del contador.
Con todo esto podremos ver su funcionamiento que es correcto, que se comunican correctamente:
import { component$, useSignal } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
import { QButton, QDisplay } from "~/integrations/react/button-display";
export default component$(() => {
console.log('Qwik Render');
const count = useSignal(0);
return (
<div class="container center">
<h2>Comunicación con dos componentes de React</h2>
<QButton
onClick$={() => {
console.log('click', count.value);
count.value++;
}}/>
<QDisplay count={count.value} />
</div>
);
});
export const head: DocumentHead = {
...
};
Ahora si vamos a la ruta principal esto será lo que vamos a ver cuando ejecutemos la acción de poner el cursor sobre el botón y realizar varios clicks con la aplicación ya hidratada:
(1
) Ejecución del botón después de poner el cursor, recibir la hidratación e interactuar.
(2
) El componente de React convertido a Qwik para la funcionalidad del botón.
(3
) Donde están los dos componentes, el botón y el componente donde se muestra el valor del contador.
(4
) El componente de React convertido a Qwik para la funcionalidad del elemento para mostrar el resultado del contador.
Con esto, ya podríamos hacer que funcione este código en Qwik con 2 o más componentes de React respetando su funcionalidad.
Pasamos al siguiente punto, donde os enseño a hacerlo con un paquete de NPM cualquiera aprovechando que React tiene infinidad de paquetes.
Os propongo como paquete para el desarrollo, el siguiente donde implementamos un calendario y lo hacemos mediante el paquete react-day-picker
.
Me ha parecido una opción super interesante, ya que es un paquete con una funcionalidad útil, muy bien documentado (por lo que lo podremos exprimir) y que a la vez nos va a servir para enfocarlo en el capítulo.
Con todo lo que veremos seremos capacesde poder usarlo en cualquier proyecto que necesite esta funcionalidad en el futuro.
Enlaces de interés -
react-day-picker
Información completa que necesitamos para trabajar con este paquete:
- NPM: https://shorten-up.vercel.app/opRPaxN2qG
- Documentación oficial: https://shorten-up.vercel.app/CpIB89pvpB
Después de trabajar con ello, os animo a que probéis con otros paquetes de React que podráis encontrar, para poder practicar.
Lo que vamos a hacer en Qwik ahora mismo es seguir las indicaciones de la documentación oficial que ya he añadido para que accedáis.
Actualmente (principios del 2024) se encuentra trabajando en la versión 8, por lo que lo desarrollado se realizará de la siguiente manera.
Comenzamos con la instalación de las dependencias necesarias
npm install react-day-picker@8 date-fns
Una vez instaladas las dependencias, vamos al directorio src/integrations/react
donde estamos añadiendo todos los componentes de React para realizar la conversión a Qwik y creamos el directorio date-picker
(src/integrations/react/date-picker
) o como le queramos llamar junto con los dos fichero tsx que serán react.tsx
y el fichero index.tsx
.
Una vez creados los ficheros añadimos lo siguiente en react.tsx
donde estaremos configurando el componente de React basándonos en la referencia del ejemplo inicial:
https://shorten-up.vercel.app/Hi_wUcIH9D
Implementamos el siguiente código:
/** @jsxImportSource react */
import { format } from 'date-fns';
import { useState } from 'react';
import { DayPicker } from 'react-day-picker';
export function DayPickerReact() {
const [selected, setSelected] = useState<Date>();
let footer = <p>Please pick a day.</p>;
if (selected) {
footer = <p>You picked {format(selected, 'PP')}.</p>;
}
return (
<DayPicker
mode='single'
selected={selected}
onSelect={setSelected}
footer={footer}
/>
);
}
Y lo que respecta a index.tsx
, ahora en la propiedad eagerness
, añadiremos la opción visible
, para que se hidrate cuando sea visible el contenido de la ruta donde estará el componente.
import { qwikify$ } from '@builder.io/qwik-react';
import { DayPickerReact } from './react';
// Convertimos el componente React en un componente Qwik
export const QDayPickerReact = qwikify$(DayPickerReact, {
eagerness: 'visible',
});
Como veis, la conversión es igual a lo realizado con un componente local propio, como se ha visto anteriormente.
Al ver esta opción ya nos va a abrir un montón de posibilidades, para así poder reutilizar muchos componentes de React existentes, sin tener que andar creándolos de nuevo adaptados a Qwik.
Ahora vamos a src/routes/index.tsx
y lo que tenemos que hacer es crear un componente nuevo usando el componente <QDayPickerReact />
para añadirle los estilos del componente (como se indica en su documentación).
Quedará de la siguiente forma:
import { component$, useStyles$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
// el componente de NPM ya convertido en Qwik
import { QDayPickerReact } from "~/integrations/react/date-picker";
// Estilos del componente que acabamos de incluir en el proyecto
import dayPickerStyles from "./../../node_modules/react-day-picker/dist/style.css?inline";
export default component$(() => {
useStyles$(dayPickerStyles);
return (
<div class="container center">
<h2>Trabajando con un paquete NPM - React Day Picker</h2>
<QDayPickerReact />
</div>
);
});
export const head: DocumentHead = {
....
};
Y este será el resultado actual (en el momento de realizar la redacción inicial del capítulo, es 29/08/2023
como se indica en el calendario):
Es evidente que el estilo no es muy chulo, pero ese no es el objetivo. Si probamos la funcionalidad, seleccionando cualquier día se añadirá abajo donde pone el texto Please pick a day
. Seleccionaremos el 27/12/2023
por ejemplo siendo el siguiente resultado:
Como se puede observar, esto funciona perfectamente y llegados a este punto, ya tenemos lo básico y necesario para cumplir el objetivo del capítulo.
Aun habiendo cuplido el objetivo, os invito a que sigáis leyendo, ya que os voy a mostrar otro ejemplo con un paquete similar.
Os propongo otro paquete para el desarrollo de componentes de calendario usando librerías de React.
Este paquete de NPM como el del apartado anterior lo he encontrado un poco curioseando para este capítulo.
Aparte de enseñaros a como consumirlo os voy a mostrar la manera de obtener la información del estado de lo que seleccionemos, para que podamos usar esa información dentro del estado de Qwik y así realizar las operaciones necesarias para nuestros proyectos y así acercarse a lo que es más las necesidades reales de un proyecto normal.
Me ha parecido una opción super interesante y que está muy bien documentado (por lo que lo podremos exprimir) y que a la vez nos va a servir para enfocarlo en el capítulo.
Ya tenemos los enlaces del paquete y su documentación para que profundicemos.
¿Qué vamos a hacer con este paquete de React?
Usaremos la documentación oficial
Os animo que veáis los siguientes apartados y experimentéis y mejoréis lo que veamos en este punto. Toda la información la usaremos partiendo desde esta referencia:
https://shorten-up.vercel.app/YidDe0TI1i
En todos estos casos, habilitaremos la opción para recibir el valor en nuestro componente inicial de la ruta y actualizar el estado, para que podamos hacer con esa información lo que queramos, ya que igual nos interesa almacenarla en alguna base de datos o lo usamos para hacer un cálculo.
Nota de interés
Después de trabajar con este paquete, os animo a que probéis con otros paquetes de React que podráis encontrar, para poder practicar.
En NPM podemos buscar por tag, todo lo etiquetado como
React
mediante este enlace:
https://www.npmjs.com/search?q=keywords:react
(Hay más de 100000 paquetes disponibles)
Una vez visto los aspectos previos a empezar a trabajar con ello, comencemos con la instalación y primeras configuraciones iniciales basándonos en la documentación oficial.
Ejecutamos el comando de instalar las dependencias:
npm i react-modern-calendar-datepicker@3.1.6
Una vez instaladas las dependencias, vamos al directorio src/integrations/react
y creamos el directorio principal para esta librería que lo llamaremos calendar-datepicker
(src/integrations/react/calendar-datepicker
) o como le queramos llamar.
Dentro de este directorio creamos un fichero index.tsx
principal, donde vamos a exportar los diferentes componentes que se creen dentro de este directorio, para hacer que los imports sean lo más limpios posibles al consumirlos.
Una vez realizados los pasos anteriores, creamos dentro de este nuevo directorio un nuevo directorio llamado default
que será donde añadamos la funcionalidad básica y una vez realizado esto añadimos los dos fichero tsx que serán react.tsx
y el fichero index.tsx
.
La estructura con estos cambios será la siguiente:
src/
└── integrations/
└── react/
└── calendar-datepicker/
|── index.tsx
└── default/
|── index.tsx
└── react.tsx
Una vez creados los ficheros añadimos lo siguiente en react.tsx
donde estaremos configurando el componente de React basándonos en la referencia que tenemos a continuación.
Single Date Default Value
: Calendario, con una fecha seleccionada por defecto. Nos permite seleccionar cualquier otra fecha dependiendo de nuestras necesidades y deseo: https://shorten-up.vercel.app/hejpGUz56XY este será el código a implementar en ese fichero src/integrations/react/calendar-datepicker/default/react.tsx
:
/** @jsxImportSource react */
import { useState } from "react";
import { Calendar } from "react-modern-calendar-datepicker";
export const CalendarReactPicker = () => {
const defaultValue = {
year: 2019,
month: 10,
day: 5,
};
const [selectedDay, setSelectedDay] = useState(defaultValue);
return (
<>
<Calendar
value={selectedDay}
onChange={setSelectedDay}
shouldHighlightWeekends
/>
<p>
Día seleccionado: <br/>{selectedDay.day.toString().padStart(2, "0")}/
{selectedDay.month.toString().padStart(2, "0")}/{selectedDay.year}
</p>
</>
);
};
Y lo que respecta a index.tsx
, ahora en la propiedad eagerness
, añadiremos la opción hover
, para que se hidrate cuando pongamos el cursor del ratón sobre el, haciendo que ya esté disponible su funcionalidad para su uso.
import { qwikify$ } from '@builder.io/qwik-react';
import { CalendarReactPicker } from './react';
// Convertimos el componente React en un componente Qwik
export const QCalendarReactPicker = qwikify$(CalendarReactPicker, {
eagerness: 'hover',
});
Como se puede observar, la conversión es igual a que si la hiciésemos de un componente local propio como se ha visto anteriormente.
Esto supone una gran ventaja de cara a migrar un proyecto de React a Qwik, sin realizar un esfuerzo inhumano.
Antes de ir al fichero src/routes/index.tsx
para habilitar el acceso a estos componentes desde el punto de entrada de src/integrations/react/calendar-datepicker
vamos al fichero index.tsx
que se encuentra en este apartado y añadimos lo siguiente:
export * from './default';
Ahora vamos a src/routes/index.tsx
y realizamos los siguientes pasos:
Teniendo en cuenta estos pasos, quedará de la siguiente forma:
import { component$, useStyles$ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
// 1.- el componente de NPM ya convertido en Qwik
import {
QCalendarReactPicker
} from "~/integrations/react/calendar-datepicker";
// 2.- Estilos del componente que acabamos de incluir en el proyecto
import calendarDatePicker from "./../../node_modules/react-modern-calendar-datepicker/lib/DatePicker.css?inline";
export default component$(() => {
// 3.- Estilos
useStyles$(calendarDatePicker);
// 4.- Consumir nuestro componente nuevo
return (
<>
<h1>React Modern Calendar</h1>
<hr />
<div>
<h2>Básico (Fecha fija de inicio)</h2>
<QCalendarReactPicker />
<hr />
</div>
</>
);
});
export const head: DocumentHead = {
....
};
Y este será el resultado teniendo en cuenta que se ha fijado la fecha 05/10/2019 (5 de octubre de 2019):
Si seleccionamos otra fecha, el cambio debe de efectuarse añadiendo la nueva fecha en formato DD/MM/AAAA
:
Como podéis observar, ya se ha completado el primer ejemplo, vamos a por los siguientes.
Una vez conseguido el ejemplo más sencillo y ver que funciona correctamente pasamos a este punto, donde vamos a trabajar con la selección de un rango de fechas seleccionando la fecha de inicio y final.
Esta opción va a super útil para marcar una agrupación de fechas como por ejemplo para marcar periodos vacacionales u otros que nos puedan servir con esta funcionalidad.
Manteniendo la estructura del apartado anterior, ahora vamos a crear un directorio llamado range
que estará compuesto por los mismos ficheros, siendo esta la estructura actual con los cambios que vamos a aplicar y lo trabajado antes.
src/
└── integrations/
└── react/
└── calendar-datepicker/
|── index.tsx
|── default/
| |── index.tsx
| └── react.tsx
└── range/ # Esto es lo nuevo
|── index.tsx
└── react.tsx
Aplicamos los cambios (basándonos en la documentación) en el fichero react.tsx
de la funcionalidad (range
):
/** @jsxImportSource react */
import { useState } from "react";
import { Calendar } from "react-modern-calendar-datepicker";
export const CalendarReactRangePicker = () => {
// Día desde donde iniciamos
const defaultFrom = {
year: 2019,
month: 3,
day: 4,
};
// Día hasta donde llegamos (incluido)
const defaultTo = {
year: 2019,
month: 3,
day: 7,
};
const defaultRange = {
from: defaultFrom,
to: defaultTo,
};
const [selectedDayRange, setSelectedDayRange] = useState(defaultRange);
return (
<>
<Calendar
value={selectedDayRange}
onChange={setSelectedDayRange}
shouldHighlightWeekends
/>
{selectedDayRange && selectedDayRange.from ? (
<p>
Desde: <br />
{selectedDayRange.from.day.toString().padStart(2, "0")}/
{selectedDayRange.from.month.toString().padStart(2, "0")}/
{selectedDayRange.from.year}
</p>
) : (
<span>Cargando...</span>
)}
{selectedDayRange && selectedDayRange.to ? (
<p>
Hasta: <br />
{selectedDayRange.to.day.toString().padStart(2, "0")}/
{selectedDayRange.to.month.toString().padStart(2, "0")}/
{selectedDayRange.to.year}
</p>
) : (
<span>Cargando...</span>
)}
</>
);
};
Y convirtiendo el componente a Qwik en el fichero index.tsx
:
import { qwikify$ } from '@builder.io/qwik-react';
import { CalendarReactRangePicker } from './react';
// Convertimos el componente React en un componente Qwik
export const QCalendarReactRangePicker = qwikify$(CalendarReactRangePicker, {
eagerness: 'hover',
});
Y añadimos el componente en el index.tsx
que tenemos en la raíz de calendar-datepicker
, para dar acceso a ese componente simplificando el contenido del import en src/routes/index.tsx
:
export * from './default';
export * from './range'; // <==== Lo trabajado ahora
Una vez realizados todos los pasos para crear la funcionalidad vamos a consumirla en src/routes.index
manteniendo los mismos pasos anteriores:
...
// 1.- el componente de NPM ya convertido en Qwik
import {
QCalendarReactRangePicker
} from "~/integrations/react/calendar-datepicker";
// 2.- Estilos del componente que acabamos de incluir en el proyecto
...
export default component$(() => {
// 3.- Estilos
...
// 4.- Consumir nuestro componente nuevo
return (
<>
<h1>React Modern Calendar</h1>
<hr />
<div>
<h2>Rango</h2>
<QCalendarReactRangePicker />
<hr />
</div>
</>
);
});
export const head: DocumentHead = {
....
};
Y la apariencia será la siguiente teniendo en cuenta que la fecha de inicio se ha definido el 04/03/2019
y la final el 07/03/2019
:
Os animo a que hagáis varias pruebas haciendo selecciones de rango, comparando a ver si se comporta igual que en lo que vemos en la documentación:
Con la primera fecha seleccionada únicamente.
La fecha final seleccionada (mes de junio):
Esta sería una pequeña prueba, pero podéis hacer las que veáis oportunas.
Actualmente ya hemos conseguido crear las dos primeras funcionalidades, y con ello vamos interiorizando más a base de ejemplos como se hace el proceso de conversión y su uso.
Para terminar con estas opciones principales, vamos a realizar una nueva funcionalidad que consistirá en hacer selecciones múltiples de fechas individuales.
Manteniendo la estructura del apartado anterior, ahora vamos a crear un directorio llamado multiple
que estará compuesto por los mismos ficheros base de siempre, siendo esta la estructura actual con los cambios que vamos a aplicar y lo trabajado antes.
src/
└── integrations/
└── react/
└── calendar-datepicker/
|── index.tsx
|── default/
| |── index.tsx
| └── react.tsx
|── range/
| |── index.tsx
| └── react.tsx
└── multiple/ # Esto es lo nuevo
|── index.tsx
└── react.tsx
Aplicamos los cambios (basándonos en la documentación) en el fichero react.tsx
de la funcionalidad (multiple
):
/** @jsxImportSource react */
import { useState } from "react";
import { Calendar } from "react-modern-calendar-datepicker";
export const CalendarReactMultiplePicker = () => {
const preselectedDays = [
{
year: 2019,
month: 10,
day: 2,
},
{
year: 2019,
month: 10,
day: 15,
},
{
year: 2019,
month: 10,
day: 30,
},
];
const [selectedDayRange, setSelectedDayRange] = useState(preselectedDays);
return (
<>
<Calendar
value={selectedDayRange}
onChange={setSelectedDayRange}
shouldHighlightWeekends
/>
<ul>
{selectedDayRange &&
selectedDayRange.map((selectedDay, index) => {
return (
<li key={index + "_multiple"}>
{selectedDay.day.toString().padStart(2, "0")}/
{selectedDay.month.toString().padStart(2, "0")}/
{selectedDay.year}
</li>
);
})}
</ul>
</>
);
};
Y convirtiendo el componente a Qwik en el fichero index.tsx
:
import { qwikify$ } from '@builder.io/qwik-react';
import { CalendarReactMultiplePicker } from './react';
// Convertimos el componente React en un componente Qwik
export const QCalendarReactMultiplePicker = qwikify$(CalendarReactMultiplePicker, {
eagerness: 'hover',
});
Añadimos el componente en el index.tsx
que tenemos en la raíz de calendar-datepicker
, para dar acceso a ese componente simplificando el contenido del import en src/routes/index.tsx
:
export * from './default';
export * from './range';
export * from './multiple'; // <==== Lo trabajado ahora
Una vez realizados todos los pasos para crear la funcionalidad vamos a consumirla en src/routes.index
manteniendo los mismos pasos anteriores:
...
// 1.- el componente de NPM ya convertido en Qwik
import {
QCalendarReactMultiplePicker
} from "~/integrations/react/calendar-datepicker";
// 2.- Estilos del componente que acabamos de incluir en el proyecto
...
export default component$(() => {
// 3.- Estilos
...
// 4.- Consumir nuestro componente nuevo
return (
<>
<h1>React Modern Calendar</h1>
<hr />
<div>
<h2>Selección múltiple</h2>
<QCalendarReactMultiplePicker />
<hr />
</div>
</>
);
});
export const head: DocumentHead = {
....
};
Y la apariencia será la siguiente teniendo en cuenta que las fechas individuales seleccionadas por defecto son:
02/10/2019
15/10/2019
30/10/2009
Os animo a que hagáis varias pruebas haciendo selecciones inidivuales en diferentes meses, semanas,... y veréis como se comporta.
Hasta este momento, con estas implementaciones aunque actualicemos los valores del estado de los componentes lo estamos realizando dentro de los componentes de React y no tenemos acceso a esos cambios en nuestra aplicación de Qwik.
¿Cómo podemos tener esa información disponible y actualizado en todo momento?
Es más sencillo, de lo que pensáis, trabajaremos con este último componente visto (calendar-datepicker/multiple
) y vamos a modificar el fichero react.tsx
añadiendo algunos cambios:
props
necesarios del listado de días preseleccionadosPara no cambiar lo anterior cogemos ese mismo directorio y mediante una copia creamos uno nuevo en la carpeta multiple-plus
quedando como src/integrations/react/calendar-datepicker/multiple-plus
.
A partir de este momento seguimos en este apartado haciendo las modificaciones necesarias.
Comenzamos cambiando el valor de las constantes tanto en el fichero index.tsx
y react.tsx
:
react.tsx
Desde esto:
export const CalendarReactMultiplePicker
Pasamos añadiéndole Plus
a lo siguiente:
export const CalendarReactMultiplePlusPicker
index.tsx
Desde esto:
export const QCalendarReactMultiplePicker
Pasamos añadiéndole Plus
a lo siguiente:
export const QCalendarReactMultiplePlusPicker
Teniendo en cuenta estos cambios iniciales, profundizamos añadiendo otros cambios, que empezaremos dentro de src/integrations/react/calendar-datepicker/multiple-plus/react.tsx
añadiendo lo siguiente para realizar los cambios necesarios:
/** @jsxImportSource react */
// 1.- Para asigarnlo en el prop de la función
import { QRL } from "@builder.io/qwik";
...
// 2.- Para especificar la estructura del prop
interface DayItem {
year: number;
month: number;
day: number;
}
interface MultipleSelectionsProps {
selections: Array<DayItem>;
setSelectedDayRange: QRL<(daySelections: Array<DayItem>) => void>;
}
// 3.- Adaptamos al prop
export const CalendarReactMultiplePlusPicker = (props: MultipleSelectionsProps) => {
// 4.- Asignamos con lo que traemos del prop
const { selections, setSelectedDayRange: selectMultipleDays } = props;
// 5.- Aquí asignaremos el nuevo listado de días seleccionados para actualizar
// mediante "selectMultipleDays" función renomabrada que traemos mediante props
const changeValue = (value: DayItem[]) => {
console.log("change", value);
// Actualiza el valor en el propio componente React que estamos usando en este momento
setSelectedDayRange(value);
// Actualizar la información principal del componente de ruta qwik
selectMultipleDays(value);
};
// 6.- Asignamos el valor de los elementos preseleccionados obtenidos desde los props
const [selectedDayRange, setSelectedDayRange] = useState(selections);
return (
<>
<Calendar
value={selectedDayRange}
onChange={changeValue} // <========= 7
shouldHighlightWeekends
/>
(SIN CAMBIOS)
</>
);
};
Una vez realizados los cambios en el componente React, adaptamos el componente que corresponde a la ruta inicial (src/routes/index.tsx
) especificando el valor inicial de la lista de días seleccionados (lo que teníamos antes en src/integrations/react/calendar-datepicker/multiple/react.tsx
), definimos la función donde se asignan los cambios desde el hijo y vamos a renderizar con la cantidad de elementos que se almacenan en este listado.
import { component$, useSignal, useStyles$, $ } from "@builder.io/qwik";
import type { DocumentHead } from "@builder.io/qwik-city";
// El componente de NPM ya convertido en Qwik
import {
QCalendarReactMultiplePlusPicker,
} from "~/integrations/react/calendar-datepicker";
// Estilos del componente que acabamos de incluir en el proyecto
import calendarDatePicker from "./../../node_modules/react-modern-calendar-datepicker/lib/DatePicker.css?inline";
export default component$(() => {
// 1.- Iniciamos el lista de días preseleccionados
const preSelectIndividualMultiple = useSignal([
{
year: 2019,
month: 10,
day: 2,
},
{
year: 2019,
month: 10,
day: 15,
},
{
year: 2019,
month: 10,
day: 30,
},
]);
// Estilos
useStyles$(calendarDatePicker);
// 4.- Función que pasaremos el componente React que es el hijo para actualizar el estado actual
const setRange = $((value: any) => {
console.log("router/index.tsx", value);
preSelectIndividualMultiple.value = value;
});
// 5.- En "QCalendarReactMultiplePicker" pasamos el estado y función
return (
<>
<h1>React Modern Calendar</h1>
<div>
<h2>Múltiples selecciones</h2>
<p>Elementos seleccionados: {preSelectIndividualMultiple.value.length}</p>
<br/>
<QCalendarReactMultiplePlusPicker
selections={preSelectIndividualMultiple.value}
setSelectedDayRange={setRange}
/>
<hr />
</div>
</>
);
});
export const head: DocumentHead = {
...
};
Guardamos los cambios y visualizamos los cambios, donde se puede apreciar lo que es el calendario y la parte inferior donde se visualiza el lista de fechas seleccionado, sigue intacto. Lo que se ha añadido nuevo es donde pone el mensaje Elementos seleccionados
que es el apartado que mostrará el número de días seleccionados (por defecto serán 3 junto con los valores de abajo). A medida que vayamos añadiendo / restando elementos, ese número irá acorde a esos cambios. Al guardar los cambios, esto es lo que se verá:
1
: esto es lo que coge en src/routes/index.tsx
lo que corresponde al estado inicial que pasamos hacia el componente convertido.2
: Esto es el componente de src/integrations/react/calendar-datepicker/multiple-plus/index.tsx
que consumimos del componente original adaptado en src/integrations/react/calendar-datepicker/multiple-plus/react.tsx
.Si hiciesemos click en un nuevo día, se selecciona y a la vez se actualiza el componente de Qwik correspondiente a la ruta donde estamos consumiendo el componente adaptado de React.
Debemos de observar en el Inspector de Elementos
de nuestro navegador los cambios que se da en el estado en el fichero src/integrations/react/calendar-datepicker/multiple-plus/react.tsx
cuando añadimos o restamos un día de nuestra selección y a su vez, se puede observar como se recibe la información del cambio de estado en la ruta principal donde estamos consumiendo el componente de React convertido.
Hacemos click sobre un día:
Al seleccionarlo cambia el número de total de Elementos seleccionados
a 4 y en el Inspectos de elementos
se muestra de esta manera:
Y llegados a este punto, podríamos decir que ya somos capaces de consumir componentes de React desde lo más básicos hasta los que funcionan con estado y también habilitando la opción de actualizar el estado principal en Qwik.
Resultado de lo trabajado en este capítulo
El código que encontráis es el resultado final de todo el proceso realizado durante el capítulo. Os recomiendo que vayáis haciendo los pasos poco a poco y paso a paso para ir interiorizando todos los conceptos y en este caso, pienso que os vendría bien realizar pruebas con más ejemplos, proponiendo alguna funcionalidad real y que sea útil.
@builder.io/qwik-react
de manera manual.Hemos trabajado con la opción de integrar componentes de React en diferentes formas, con lo que esto nos abre las puertas a poder hacer una migración menos dolorosa a Qwik de desarrollos de React sin tener que hacer un trabajo tan duro como el que habría que realizar si tenemos que reescribir todas las funcionalidades.