Skip to content

Componentes Integrados

PreffX incluye cuatro componentes integrados accesibles a través del segundo argumento (utils) de cada componente. Resuelven patrones comunes de UI sin dependencias adicionales.

ComponentePropósitoDisponibilidad
ForRenderizar listas con elementos reactivosutils.For
CatchCapturar y manejar errores de renderizadoutils.Catch
PortalRenderizar hijos en un nodo DOM diferenteutils.Portal
DeferMostrar un marcador de posición hasta que un valor esté listoutils.Defer

For

Renderizador de listas reactivo. Acepta una señal de elementos (o un array simple) y solo vuelve a renderizar los elementos que cambian. Soporta un fallback opcional que se muestra cuando la lista está vacía.

Firma de tipo

typescript
const For: PC<{
    items: Signal<any[]>;
    callback: (item: any) => any;
    fallback?: any;
}>

Propiedades

PropiedadTipoRequeridoDescripción
itemsSignal<any[]>Una señal que contiene el array de elementos. Cuando la señal cambia, For reconcilia eficientemente la lista — los elementos añadidos se renderizan, los elementos eliminados se limpian.
callback(item: any) => anyFunción llamada para cada elemento. Devuelve el JSX a renderizar para ese elemento. El valor devuelto se almacena en caché y solo se recalcula cuando el valor del elemento en sí cambia.
fallbackanyNoContenido renderizado cuando el array está vacío.

Uso

tsx
import type { PC } from 'preffx';

type Item = { id: number; name: string };

const App: PC = (_, { signal, For }) => {
    const items = signal<Item[]>([
        { id: 1, name: 'Alpha' },
        { id: 2, name: 'Beta' },
        { id: 3, name: 'Gamma' },
    ]);

    return (
        <ul>
            <For
                items={items}
                callback={(item: Item) => (
                    <li key={item.id}>{item.name}</li>
                )}
                fallback={<li>Sin elementos</li>}
            />
        </ul>
    );
};

Actualizaciones reactivas

Cuando mutas el array en la señal, el DOM se actualiza automáticamente:

tsx
// Añadir un elemento
items.value = [...items.value, { id: 4, name: 'Delta' }];

// Eliminar un elemento
items.value = items.value.filter((i) => i.id !== 2);

// Reemplazar todos los elementos
items.value = [];
// -> se muestra el fallback "Sin elementos"

Elementos señal dentro del array

Cada elemento del array puede ser a su vez una señal. Cuando el valor de la señal de un elemento cambia, solo se recalcula la salida de ese elemento:

tsx
const items = signal([
    signal({ id: 1, name: 'Alpha' }),
    signal({ id: 2, name: 'Beta' }),
]);

// Actualizar el segundo elemento — solo se vuelve a renderizar "Beta"
items.value[1].value = { id: 2, name: 'BETA' };

Catch

Captura errores lanzados (o devueltos) por componentes hijos y renderiza un contenido alternativo en su lugar. Inspirado en los Límites de Error de React.

Firma de tipo

typescript
const Catch: PC<{
    fallback?: any;
    children?: any | any[];
}>

Propiedades

PropiedadTipoRequeridoDescripción
childrenany | any[]NoHijos a renderizar. Si algún hijo es (o devuelve) una instancia de Error, el error es capturado.
fallbackanyNoContenido a mostrar cuando se capturan errores. Puede ser un valor estático o un componente función que recibe { errors, ...extraProps }.

Fallback como contenido estático

tsx
import type { PC } from 'preffx';

const App: PC = (_, { Catch }) => {
    return (
        <div>
            <Catch fallback={<div>Algo salió mal</div>}>
                <MaybeBrokenComponent />
            </Catch>
        </div>
    );
};

Fallback como componente función

La función fallback recibe errors (el array de errores capturados) más cualquier propiedad adicional pasada a <Catch>:

tsx
const App: PC = (_, { Catch }) => {
    return (
        <Catch
            fallback={({ errors }) => (
                <div class="error-fallback">
                    <h3>Ocurrió(eron) {errors.length} error(es)</h3>
                </div>
            )}
        >
            <UnstableComponent />
        </Catch>
    );
};

Pasando propiedades extra al fallback

tsx
const App: PC = (_, { Catch }) => {
    return (
        <Catch
            fallback={({ errors, severity }) => (
                <div class={severity}>
                    Errores: {errors.length}
                </div>
            )}
            severity="critical"
        >
            <UnstableComponent />
        </Catch>
    );
};

Cómo se detectan los errores

Catch escanea todos los hijos recursivamente (usando flat(Infinity)). Cualquier hijo que sea una instancia de Error (mediante instanceof Error) activa el contenido alternativo.


Portal

Renderiza hijos en un nodo DOM diferente fuera de la raíz del componente. Útil para modales, tooltips, menús desplegables y notificaciones.

Firma de tipo

typescript
const Portal: PC<{
    root: HTMLElement;
    children?: any | any[];
}>

Propiedades

PropiedadTipoRequeridoDescripción
rootHTMLElementEl elemento DOM de destino donde se deben renderizar los hijos.
childrenany | any[]NoContenido a renderizar dentro de la raíz del portal.

Uso básico

tsx
import type { PC } from 'preffx';

const Modal: PC = (_, { Portal }) => {
    const portalRoot = document.getElementById('modal-root')!;
    return (
        <Portal root={portalRoot}>
            <div class="modal-overlay">
                <div class="modal-content">¡Hola desde el portal!</div>
            </div>
        </Portal>
    );
};

Contenido reactivo dentro de un portal

Los hijos dentro de un portal son completamente reactivos — las señales los actualizan automáticamente:

tsx
const Notification: PC<{ message: string }> = (
    { message },
    { signal, Portal },
) => {
    const count = signal(0);

    return (
        <div>
            <Portal root={document.getElementById('toast-root')!}>
                <span id="notification-count">{count}</span>
            </Portal>
            <button onClick={() => (count.value += 1)}>+</button>
        </div>
    );
};

Limpieza

Cuando el componente padre es destruido, Portal limpia automáticamente sus hijos y vacía la raíz del portal mediante root.replaceChildren().

Sin raíz

Si root es null o undefined, Portal no renderiza nada:

tsx
<Portal root={null} children={<span>No aparecerá</span>} />
// no renderiza nada

Defer

Muestra un valor de marcador de posición inicial mientras se prepara el valor real (por ejemplo, esperando que se resuelva una Promise, o que una señal reciba un valor que no sea una Promise).

Firma de tipo

typescript
const Defer: PC<{
    initial?: any;
    value: Signal<any>;
}>

Propiedades

PropiedadTipoRequeridoDescripción
initialanyNoContenido de marcador de posición renderizado mientras value contiene una Promise o aún no está listo.
valueSignal<any>Una señal cuyo valor es observado. Cuando el valor resuelto de la señal no es una Promise, reemplaza el contenido initial.

Diferir una Promise

tsx
import type { PC } from 'preffx';

const SlowData: PC = (_, { signal, Defer }) => {
    const data = signal(
        new Promise<string>((resolve) =>
            setTimeout(() => resolve('¡Cargado!'), 2000)
        )
    );

    return (
        <Defer
            initial={<div>Cargando...</div>}
            value={data}
        />
    );
};

Secuencia de renderizado:

  1. Muestra inmediatamente Cargando...
  2. Después de 2 segundos, cuando la Promise se resuelve, muestra ¡Cargado!

Diferir un valor simple (no Promise)

Si la señal ya contiene un valor simple (no una Promise), Defer lo renderiza inmediatamente — se omite initial:

tsx
<Defer
    initial="Esperando..."
    value={signal('Contenido real')}
/>
// Renderiza inmediatamente "Contenido real"

Cambio reactivo de Promise a valor simple

Cuando una señal cambia de una Promise a un valor simple, Defer cambia del marcador de posición al contenido real:

tsx
const DynamicDefer: PC = (_, { signal, effect, Defer, onMount }) => {
    const value = signal(new Promise(() => {})); // Promise pendiente

    onMount(() => {
        setTimeout(() => {
            value.value = '¡Finalmente resuelto!';
        }, 1000);
    });

    return <Defer initial="Cargando..." value={value} />;
};

Array de hijos mediante Defer

Defer funciona con cualquier contenido, incluyendo arrays de nodos JSX:

tsx
const items = signal([
    <span id="a">Alpha</span>,
    <span id="b">Beta</span>,
]);

return (
    <Defer
        initial={[<div>Cargando...</div>]}
        value={items}
    />
);
// Renderiza inmediatamente <span>Alpha</span><span>Beta</span>

Limpieza

Cuando el componente padre es destruido, Defer limpia automáticamente su seguimiento interno de señales.

Publicado bajo la Licencia Apache 2.0