Cheatsheet

Comenzamos con el capítulo final, donde se recopilan los aspectos más importantes a tener en cuenta para poder seguir trabajando con Qwik.

Se añadirán las notas sobre cosas a tener en cuenta, para no tener que andar buscando en el libro página por página.

Como ayuda si se añadirá una mención de que capítulo corresponde a ese concepto, con el objetivo de facilitar el proceso de búsqueda.

Se aprovechará haciendo una comparativa de como se implementa en React, comparándolo con Qwik.

React Vs Qwik

Algunos ejemplos de código de Qwik haciendo una comparativa con React para mostrar como sería para implementar la misma funcionalidad.

Componente básico - Hola Mundo

El ejemplo más básico con el que trabajamos a la hora de utilizar un componente.

⚡️ Qwik

export const HelloWorld = component$(() => {
  return <div>Hello world</div>;
});

⚛️ React

export function HelloWorld() {
  return <div>Hello world</div>;
}

Capítulo donde se trabaja con este concepto:

Botón con acción de click mediante controlador

Componente sencillo con un botón en el que se implementa el evento click.

⚡️ Qwik

export const Button = component$(() => {
  return <button onClick$={() => console.log('click')}>Click me</button>;
});

⚛️ React

export function Button() {
  return <button onClick={() => console.log('click')}>Click me</button>;
}

Capítulos donde se trabaja con este concepto:

Using Props

Uso de un componentes sencillo con props para pasar información de manera dinámica a los componentes y poder reutilizarlos, como podría ser un componente de detalles, botones,...

⚡️ Qwik

export const Parent = component$(() => {
  const userData = useStore({ count: 0 });
  return <Child userData={userData} />;
});
 
interface ChildProps {
  userData: { count: number };
}

export const Child = component$<ChildProps>(({ userData }) => {
  return (
    <>
      <button onClick$={() => userData.count++}>Increment</button>
      <p>Count: {userData.count}</p>
    </>
  );
});

⚛️ React

export const Parent = (() => {
  const [ count, setCount ] = useState<number>(0);
  
  const increment = (() => {
    setCount((prev) => prev + 1)
  })
  return <Child count={count} increment={increment} />;
})

interface ChildProps {
  count: number;
  increment: () => void;
}

export const Child = ((props: ChildProps) => {
  return (
    <>
      <button onClick={props.increment}>Increment</button>
      <p>Count: {props.count}</p>
    </>
  );
})

Capítulos donde se trabaja con este concepto:

Declarar un estado local

Para gestionar el valor del estado local de una manera sencilla y hacer que se muestre de manera reactiva la información correspondiente a ese valor que queramos mostrar.

⚡️ Qwik

export const LocalStateExample = component$(() => {
  const count = useSignal(0);
  return <div>Value is: {count.value}</div>;
});

⚛️ React

export function UseStateExample() {
  const [value, setValue] = useState(0);
  return <div>Value is: {value}</div>;
}

Capítulo donde se trabaja con este concepto:

Crear un componente de contador básico

Ejemplo del típico ejemplo de componente en el que añadimos un elemento, para contabilizar el estado de ese contador mediante un hook acorde a ello.

⚡️ Qwik

export const Counter = component$(() => {
  const count = useSignal(0);
  return (
    <>
      <p>Value is: {count.value}</p>
      <button onClick$={() => count.value++}>Increment</button>
    </>
  );
});

⚛️ React

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <>
      <p>Value is: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
}

Capítulos donde se trabaja con este concepto:

Reloj que se actualiza segundo a Segundo

Ejemplo de un componente de reloj, donde hacemos uso de los conocimientos de crear componentes, trabajo de estados y uso de los ciclos de vida para crear un reloj que irá actualizándose segundo a segundo.

⚡️ Qwik

export const Clock = component$(() => {
  const seconds = useSignal(0);
  useVisibleTask$(({ cleanup }) => {
    const interval = setInterval(() => {
      seconds.value++;
    }, 1000);
    cleanup(() => clearInterval(interval));
  });

  return <p>Seconds: {seconds.value}</p>;
});

⚛️ React

export function Clock() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(seconds + 1);
    }, 1000);
    return () => clearInterval(interval);
  });
  return <p>Seconds: {seconds}</p>;
}

Capítulos donde se trabaja con este concepto:

Realiza una solicitud de obtención cada vez que cambia el estado

Trabajo de consumo de APIs haciendo uso de los ciclos de vida con la función track.

⚡️ Qwik

export const Fetch = component$(() => {
  const url = useSignal('https://api.github.com/repos/BuilderIO/qwik');
  const responseJson = useSignal(undefined);

  useTask$(async ({ track }) => {
    track(() => url.value);
    const res = await fetch(url.value);
    const json = await res.json();
    responseJson.value = json;
  });

  return (
    <>
      <p>{responseJson.value?.name} has {responseJson.value?.stargazers_count} ✨'s</p>
      <input name="url" bind:value={url} />
    </>
  );
});

⚛️ React

export function Fetch() {
  const [url, setUrl] = useState('https://api.github.com/repos/BuilderIO/qwik');
  const [responseJson, setResponseJson] = useState(undefined);
  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((json) => setResponseJson(json));
  }, [url]);
  return (
    <>
      <p>{responseJson?.name} has {responseJson?.stargazers_count} ✨'s</p>
      <input name="url" onInput={(ev) => setUrl((ev.target as HTMLInputElement).value)} />
    </>
  );
}

Capítulos donde se trabaja con este concepto:

Declarar un contexto y consumirlo

Para poder trabajar con los estados globales y así hacer uso de la información desde cualquier componente, sin hacer uso de los props.

⚡️ Qwik


import {
    useContext,
    useContextProvider,
    useSignal, 
    createContextId, // (A partir de la 0.18.1)
} from '@builder.io/qwik';

export const MyContext = createContextId<Signal<number>>('my-context');

export const Parent = component$(() => {
  const message = useSignal('some example value');
  useContextProvider(MyContext, message);
  return (
    <>
      <Child />
    </>
  );
});

export const Child = component$(() => {
  const message = useContext(MyContext);
  return <p>{message.value}</p>;
});

⚛️ React

export const MyContext = createContext({ message: 'some example value' });

export default function Parent() {
  return (
    <MyContext.Provider value={{ message: 'updated example value' }}>
      <Child />
    </MyContext.Provider>
  );
}

export const Child = () => {
  const value = useContext(MyContext);
  return <p>{value.message}</p>;
};

Capítulos donde se trabaja con este concepto:

Crear un campo de texto con reacción tardía

Creamos un componente con un elemento input donde implementaremos la opción con retardo, para que se asigne un valor en un tiempo especificado. En este caso el retardo será de 1000ms que es igual a 1sg.

⚡️ Qwik

export const DebouncedInput = component$(() => {
  const inputText = useSignal('');
  const debouncedValue = useSignal('');

  useTask$(({ track, cleanup }) => {
    track(() => inputText.value);

    const debounced = setTimeout(() => {
      debouncedValue.value = inputText.value;
    }, 1000);

    cleanup(() => clearTimeout(debounced));
  });

  return (
    <>
      <input bind:value={inputText} />
      <p>{debouncedValue.value}</p>
    </>
  );
});

⚛️ React

export const DebouncedInput = () => {
  const [value, setValue] = useState('');
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const debounced = setTimeout(() => setDebouncedValue(value), 1000);

    return () => {
      clearTimeout(debounced);
    };
  }, [value]);

  return (
    <>
      <input value={value} onChange={(ev) => setValue((ev.target as HTMLInputElement).value))} />
      <p>{debouncedValue}</p>
    </>
  );
};

Capítulos en los que se trabaja con estos conceptos:

Cambia el color de fondo aleatoriamente

En este caso vamos a crear un ejemplo donde mediante el evento onClick$() vamos a ejecutar una función que genera los valores de rojo, verde y azul de manera aleatoria.

⚡️ Qwik

export const DynamicBackground = component$(() => {
  const red = useSignal(0);
  const green = useSignal(0);
  const blue = useSignal(0);

  return (
    <div
      style={{
        background: `rgb(${red.value}, ${green.value}, ${blue.value})`,
      }}
    >
      <button
        onClick$={() => {
          red.value = Math.random() * 256;
          green.value = Math.random() * 256;
          blue.value = Math.random() * 256;
        }}
      >
        Change background
      </button>
    </div>
  );
});

⚛️ React

export function DynamicBackground() {
  const [red, setRed] = useState(0);
  const [green, setGreen] = useState(0);
  const [blue, setBlue] = useState(0);
  return (
    <div
      style={{
        background: `rgb(${red}, ${green}, ${blue})`,
      }}
    >
      <button
        onClick={() => {
          setRed(Math.random() * 256);
          setGreen(Math.random() * 256);
          setBlue(Math.random() * 256);
        }}
      >
        Change background
      </button>
    </div>
  );
}

Capítulos donde se trabaja con este concepto:

Renderizamos una lista de elementos

Creamos un componente que renderiza una lista de elementos. En este caso, por ejemplo mostramos una lista de equipos de la NBA con su año de fundación.

⚡️ Qwik

export const NBATeams = component$(() => {
  const nbaTeams = [
      { "name": "Atlanta Hawks",  "foundation": 1946 },
      { "name": "Boston Celtics", "foundation": 1946 },
      { "name": "Brooklyn Nets", "foundation": 1967 },
      { "name": "Charlotte Hornets", "foundation": 1988 },
      { "name": "Chicago Bulls", "foundation": 1966 },
      { "name": "Cleveland Cavaliers", "foundation": 1970 },
      { "name": "Dallas Mavericks", "foundation": 1980 },
      { "name": "Denver Nuggets", "foundation": 1967 },
      { "name": "Detroit Pistons", "foundation": 1941 },
      { "name": "Golden State Warriors", "foundation": 1946 },
      { "name": "Houston Rockets", "foundation": 1967 },
      { "name": "Indiana Pacers", "foundation": 1967 },
      { "name": "LA Clippers", "foundation": 1970 },
      { "name": "Los Angeles Lakers", "foundation": 1947 },
      { "name": "Memphis Grizzlies", "foundation": 1995 },
      { "name": "Miami Heat", "foundation": 1988 },
      { "name": "Milwaukee Bucks", "foundation": 1968 },
      { "name": "Minnesota Timberwolves", "foundation": 1989 },
      { "name": "New Orleans Pelicans", "foundation": 2002 },
      { "name": "New York Knicks", "foundation": 1946 },
      { "name": "Oklahoma City Thunder", "foundation": 1967 },
      { "name": "Orlando Magic", "foundation": 1989 },
      { "name": "Philadelphia 76ers", "foundation": 1946 },
      { "name": "Phoenix Suns", "foundation": 1968 },
      { "name": "Portland Trail Blazers", "foundation": 1970 },
      { "name": "Sacramento Kings", "foundation": 1923 },
      { "name": "San Antonio Spurs", "foundation": 1967 },
      { "name": "Toronto Raptors", "foundation": 1995 },
      { "name": "Utah Jazz", "foundation": 1974 },
      { "name": "Washington Wizards", "foundation": 1961 }
  ];
  return (
    <ul>
      {nbaTeams.map((team) => (
        <li key={team.name + team.foundation}>
          {team.name} ({team.foundation})
        </li>
      ))}
    </ul>
  );
});

⚛️ React

export function NBATeams() {
  const nbaTeams = [
      { "name": "Atlanta Hawks",  "foundation": 1946 },
      { "name": "Boston Celtics", "foundation": 1946 },
      { "name": "Brooklyn Nets", "foundation": 1967 },
      { "name": "Charlotte Hornets", "foundation": 1988 },
      { "name": "Chicago Bulls", "foundation": 1966 },
      { "name": "Cleveland Cavaliers", "foundation": 1970 },
      { "name": "Dallas Mavericks", "foundation": 1980 },
      { "name": "Denver Nuggets", "foundation": 1967 },
      { "name": "Detroit Pistons", "foundation": 1941 },
      { "name": "Golden State Warriors", "foundation": 1946 },
      { "name": "Houston Rockets", "foundation": 1967 },
      { "name": "Indiana Pacers", "foundation": 1967 },
      { "name": "LA Clippers", "foundation": 1970 },
      { "name": "Los Angeles Lakers", "foundation": 1947 },
      { "name": "Memphis Grizzlies", "foundation": 1995 },
      { "name": "Miami Heat", "foundation": 1988 },
      { "name": "Milwaukee Bucks", "foundation": 1968 },
      { "name": "Minnesota Timberwolves", "foundation": 1989 },
      { "name": "New Orleans Pelicans", "foundation": 2002 },
      { "name": "New York Knicks", "foundation": 1946 },
      { "name": "Oklahoma City Thunder", "foundation": 1967 },
      { "name": "Orlando Magic", "foundation": 1989 },
      { "name": "Philadelphia 76ers", "foundation": 1946 },
      { "name": "Phoenix Suns", "foundation": 1968 },
      { "name": "Portland Trail Blazers", "foundation": 1970 },
      { "name": "Sacramento Kings", "foundation": 1923 },
      { "name": "San Antonio Spurs", "foundation": 1967 },
      { "name": "Toronto Raptors", "foundation": 1995 },
      { "name": "Utah Jazz", "foundation": 1974 },
      { "name": "Washington Wizards", "foundation": 1961 }
  ];
  return (
    <ul>
      {nbaTeams.map((team) => (
        <li key={team.name + team.foundation}>
          {team.name} ({team.foundation})
        </li>
      ))}
    </ul>
  );
}

Capítulo donde se trabaja con este concepto:

Aspectos enfocados a Qwik

Cómo usar un hook correctamente

Formas incorrectas / correctas que tenemos para poder hacer uso de los hooks (y custom hooks) en Qwik.

useHook(); // <-- ❌ No funcionará por estar fuera de component$
     
export default component$(() => {
  useCustomHook(); // <-- ✅ Dentro de component$ y en la raíz

  if (condition) {
    useHook(); // <-- ❌ Aunque esté dentro de component$, no está en la raíz
  }

  useTask$(() => {
    useNavigate(); // <-- ❌ No podemos usar un hook dentro de otro
  });

  const myQrl = $(() => useHook()); // <-- ❌ Debe de estar en la raíz

  return <button onClick$={() => useHook()}></button>; // <-- ❌ No se puede con acciones
});
    
  // Dentro de una función denominada custom hook funciona como en component$
  function useCustomHook() {
    useHook(); // <-- ✅ Funciona por estar en raíz

    if (condition) {
      useHook(); // <-- ❌ No está en la raíz, está dentro de condición
    }
  }

Capítulo donde se trabaja con este concepto:

Esta porción de código os recomiendo que la tengáis siempre a mano mientras estéis aprendiendo. Luego, cuando ya tengáis todo bien interiorizado, inconscientemente lo aplicaréis perfectamente sin pensar mucho.

Rutas

  • Routing Avanzado — Enrutamiento mediante parámetros

Información sobre este apartado aquí.

Teniendo la siguiente distribución de directorios:

src/
  └── routes/
      ├── index.tsx                 # /
      ...
      └── product/
          └── [productId]/
                ├── index.tsx       # /product/1234
                └── details/
                    └── index.tsx   # /product/1234/details

Accedemos mediante /product/<id-del-producto> para la página del producto principal y /product/<id-del-producto>/details para los detalles del producto.

  • Obtención del parámetro ruta desde una URL

Información sobre este apartado aquí.

Usando como base la distribución de directorios anterior.

import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';

export default component$(() => {
  // Obtenemos toda la información acerca de la ubicación respecto a la navegación
  const location = useLocation();
  return (
    <div>
      <div>Product Item Details [productId] - Info page</div>
      // URL princ. + path + valor de parámetro de ruta
      <p>Href: {location.url.href}</p>   
      // path + valor de parámetro de ruta             
      <p>Pathname: {location.url.pathname}</p>    
      // Valor de parámetro de ruta    
      <p>Product Id: {location.params.productId}</p>  
    </div>
  );
});

Añadiendo en el navegador esta ruta: http://127.0.0.1:5173/product/1234/details/.

Opción Resultado
Href http://127.0.0.1:5173/product/1234/details/
Pathname /product/1234/details/
Product Id 1234
  • Capturar los parámetros mediante rutas comodín

Información sobre este apartado aquí.

Teniendo la siguiente distribución de directorios:

src/
  └── routes/
      ├── index.tsx                 # /
      ...
      └── product/
          └── [...productIds]/    
                  └── index.tsx    # /product/1234/123/121

Pudiendo obtener los ids de los productos implementando este código:

import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';

export default component$(() => {
  const location = useLocation();
  return (
    <>
      <div>Product Item [...productIds] - Principal Page</div>
      // IDs de los productos
      <p>Product Id: {JSON.stringify(location.params.productIds)}</p>
    </>
  );
});

Accedemos mediante /product/<id-del-producto-1>/.../<id-del-producto-n> para la página del producto principal con los ids seleccionados.

  • Agrupación

Información sobre este apartado aquí.

Teniendo en cuenta una estructura así:

src/
  └── routes/
        └── (products)/
                ├── detail/
                |     └── index.tsx
                └── preview/
                      └── index.tsx

Al haber añadido un directorio (<nombre-directorio), al introducir la ruta NO hay que añadir el nombre-directorio en la ruta.

Ruta introducida ¿Válida?
/detail
/preview
/products/detail
/products/preview
/products

Integraciones

Para implementar de forma muy fácil integraciones de librerías en nuestros proyectos con pocos pasos.

Para ejecutarlo y ver la lista de disponibles:

npm run qwik add

Más información al detalle: