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
SuspenseMuestra el mecanismo de reserva hasta que se reciban todos los recursos dentro de la función de devolución de llamadautils.Suspense

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[]>Sí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) => anySíFunció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
rootHTMLElementSíEl 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>Sí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.

Suspense ​

Suspense es un límite que agrupa los recursos creados dentro de su callback. Mientras cualquiera de ellos está pendiente, se renderiza fallback; cuando todos se resuelven, aparece el contenido real.

Firma de tipo ​

typescript
const Suspense: PC<{
    callback: () => any;
    fallback?: any;
}>

Props ​

PropTypeRequiredDescription
callback() => anyYesConstruye perezosamente el árbol de contenido; los recursos creados dentro de él se rastrean.
fallbackanyNoSe renderiza mientras algún recurso dentro del límite está pendiente.

Uso básico ​

tsx
import type { PC } from 'preffx';

const Profile: PC = (_, { resource }) => {
    const [user] = resource(async () => (await fetch('/api/user')).json());
    return <h1>{user.state.value?.name ?? '...'}</h1>;
};

export const ProfilePage: PC = (_, { Suspense }) => {
    return (
        <Suspense callback={() => <Profile />} fallback={<p>Loading...</p>} />
    );
};

SSR ​

En el servidor, el límite registra sus recursos para preload() e incrusta los datos resueltos; en el cliente hidrata sin re-obtener.

Publicado bajo la Licencia Apache 2.0