wandres.dev
INTEGRAR LIBRERÍAS · interoperar con JS

solid-primitives: la colección oficial

solid-primitives es el monorepo oficial de utilidades reactivas de la comunidad Solid: decenas de paquetes diminutos, tree-shakeables y probados, cada uno con un sistema de etapas de madurez y un contrato de diseno estricto. La convencion create frente a make que distingue lo reactivo con dueno de lo imperativo con baja, por que son seguros en SSR, ejemplos como createMediaQuery createEventListener debounce y makePersisted, y el criterio de ingenieria para decidir cuando reutilizar la coleccion y cuando fabricar tu propio primitivo.

⏱ 16 min

En la lección anterior aprendiste a fabricar primitivos con el molde canónico. La buena noticia es que la comunidad de Solid lleva años vaciando ese molde en una colección oficial: solid-primitives, un monorepo de decenas de paquetes minúsculos que resuelven las tareas recurrentes —media queries, listeners, antirebote, observadores, persistencia— con las esquinas ya limadas y el SSR ya contemplado. Saber qué contiene, cómo está organizada y, sobre todo, cuándo tirar de ella en lugar de escribir el primitivo tú mismo, es la diferencia entre reinventar ruedas y construir sobre hombros de gigantes.

🎯 Al terminar esta lección sabrás
  • Conocer solid-primitives como el monorepo oficial de utilidades reactivas, tree-shakeable y probado.
  • Distinguir la convención create* —reactivo con dueño— de make* —imperativo con baja—.
  • Reconocer que los paquetes son seguros en SSR y se importan de forma granular por paquete.
  • Aplicar un criterio de ingeniería para decidir entre reutilizar la colección y fabricar tu primitivo.

Qué es y cómo está organizada

solid-primitives no es una librería monolítica sino una familia de paquetes independientes, cada uno bajo el ámbito @solid-primitives/, cada uno resolviendo un problema acotado y publicándose por separado. Instalas solo el que necesitas y, como todo es tree-shakeable, tu bundle carga únicamente lo que usas. La lista es larga y crece; estos son los pilares que aparecen en casi cualquier app:

import { createMediaQuery } from "@solid-primitives/media";
import { createEventListener } from "@solid-primitives/event-listener";
import { debounce, throttle } from "@solid-primitives/scheduled";
import { makePersisted } from "@solid-primitives/storage";

const oscuro = createMediaQuery("(prefers-color-scheme: dark)");
const buscar = debounce((q: string) => lanzar(q), 300);

Cada paquete lleva además una etapa de madurez del 0 al 4 que anuncia su estabilidad: un primitivo en etapa 3 o 4 tiene API estable, pruebas y documentación; uno en etapa 0 o 1 es experimental. Esa señal te deja calibrar cuánto apoyarte en cada pieza según el riesgo que toleres, y es una de las razones por las que la colección se considera un lugar fiable del que copiar patrones aunque no la uses.

La superficie es amplia y cubre casi cualquier necesidad recurrente. Más allá de los pilares, encuentras @solid-primitives/resize-observer e @solid-primitives/intersection-observer para observar geometría y visibilidad, @solid-primitives/mouse y @solid-primitives/keyboard para entrada del usuario, @solid-primitives/timer para temporizadores con ciclo de vida, @solid-primitives/i18n para internacionalización reactiva y @solid-primitives/refs para trabajar con referencias y children. No hace falta memorizar la lista: basta recordar que, ante una tarea reactiva común, lo probable es que ya exista un paquete afinado esperándote, y que buscarlo antes de escribir cuesta menos que depurar tu versión casera.

create frente a make: la convención que lo explica todo

La colección codifica en sus nombres la distinción que ya rozaste al fabricar tus primitivos. Un primitivo create* es reactivo y con dueño: devuelve accessores o signals, rastrea sus argumentos y registra su limpieza con onCleanup, así que debe llamarse dentro de un scope reactivo. Un helper make* es imperativo y sin rastreo: hace una sola cosa, no observa argumentos reactivos y te devuelve una función de baja para que tú decidas cuándo cortar.

import { createEventListener, makeEventListener } from "@solid-primitives/event-listener";

// create*: reactivo, se limpia solo con el dueno
createEventListener(window, "resize", () => actualizar());

// make*: imperativo, te devuelve la baja para que la gestiones tu
const baja = makeEventListener(window, "scroll", () => medir());
// ... cuando quieras: baja();

La regla mental es directa: si necesitas que el primitivo reaccione a entradas cambiantes y se limpie con el componente, quieres un create*; si solo quieres enganchar algo una vez y llevarte el control de la baja, quieres un make*. Muchos paquetes ofrecen las dos variantes de la misma utilidad, y elegir bien entre ellas evita tanto las fugas como el rastreo innecesario.

flowchart TD
N[necesito una utilidad reactiva] --> Q{es comun y generica}
Q -->|si| P[usa solid-primitives]
Q -->|no es del dominio| M[fabrica tu primitivo con el molde]
P --> R{reactivo a argumentos}
R -->|si| CR[primitivo create con dueno]
R -->|no solo enganchar| MK[helper make con baja]
style P fill:#89b4fa,color:#11111b
style M fill:#f9e2af,color:#11111b
style CR fill:#a6e3a1,color:#11111b

SSR, tree-shaking y el criterio para decidir

Dos garantías hacen que la colección sea segura donde tus primitivos caseros tropiezan. La primera es el SSR: cada paquete protege sus accesos a APIs del navegador con guardas de servidor, así que un createMediaQuery en SolidStart no revienta al renderizar en el servidor —devuelve un valor por defecto sensato y despierta al hidratar—. La segunda es el tree-shaking real: al ser paquetes atómicos, importar debounce no arrastra el resto de la colección. Estas dos propiedades, más las pruebas y el mantenimiento de la comunidad, son justo las que cuesta sostener a mano y las que casi siempre inclinan la balanza hacia reutilizar.

El criterio de cuándo usarla frente a fabricar el tuyo es de ingeniería pura. Reutiliza solid-primitives cuando la necesidad es común y genérica —media queries, listeners, antirebote, persistencia, observadores—: ahí la colección ya resolvió las esquinas que tú descubrirías a golpes, incluido makePersisted, que es el createStoredSignal de producción con SSR e interoperabilidad ya resueltos. Fabrica tu propio primitivo, con el molde de la lección anterior, cuando la fuente es propia de tu dominio —un SDK interno, un protocolo de tu empresa, una política que ningún paquete cubre— o cuando estás aprendiendo y quieres ver el mecanismo desnudo. La consigna madura: reutiliza la mercancía común, construye solo aquello que te diferencia.

Varios primitivos en un mismo componente

El poder de la colección se ve cuando varios primitivos conviven en un componente sin que ninguno te obligue a gestionar su limpieza. Cada uno se ata al dueño y se retira solo al desmontar; tú te quedas con un componente declarativo donde cada línea dice qué observas, no cómo te suscribes ni cuándo te das de baja.

import { createSignal } from "solid-js";
import { createMediaQuery } from "@solid-primitives/media";
import { createEventListener } from "@solid-primitives/event-listener";
import { makePersisted } from "@solid-primitives/storage";
import { debounce } from "@solid-primitives/scheduled";

function Editor() {
  const compacto = createMediaQuery("(max-width: 640px)");
  const [borrador, setBorrador] = makePersisted(createSignal(""), { name: "nota" });
  const guardar = debounce((t: string) => enviarAlServidor(t), 500);

  createEventListener(window, "keydown", (e) => {
    if (e.key === "Escape") setBorrador("");   // se limpia solo al desmontar
  });

  return (
    <textarea
      classList={{ compacto: compacto() }}
      value={borrador()}
      onInput={(e) => { setBorrador(e.currentTarget.value); guardar(e.currentTarget.value); }}
    />
  );
}

Cuatro paquetes distintos colaboran —consulta de medios, persistencia, antirebote, escucha de teclado— y ninguno filtra un listener ni te pide un onCleanup explícito, porque cada uno lo lleva dentro. Ese es el dividendo de apoyarse en primitivos bien hechos: el ruido del ciclo de vida desaparece del componente y aflora la intención.

📱

media y event-listener

createMediaQuery createBreakpoints createEventListener resuelven consultas de medios y escucha de eventos con SSR contemplado.

⏱️

scheduled

debounce throttle y createScheduled encapsulan el control temporal que antes copiabas de un gist a otro.

💾

storage

makePersisted persiste un signal en localStorage o el almacen que le des, con la hidratacion ya pensada.

💡
Aunque no la uses, léela: es un catálogo de patrones canónicos

El código de solid-primitives es una de las mejores escuelas de reactividad de Solid que existen. Cada paquete es una aplicación pequeña y pulida del cuarteto que aprendiste —signal, suscripción, limpieza, API mínima— resuelta por gente que conoce las esquinas. Cuando dudes de cómo estructurar un primitivo propio, abre el que más se le parezca en la colección y estúdialo: verás la convención create/make, las guardas de SSR y el manejo de argumentos reactivos aplicados con rigor, y saldrás con un patrón que copiar aunque decidas no depender del paquete.

📝
El sistema de etapas te dice cuánto fiarte

Cada paquete anuncia una etapa del 0 al 4 que resume su madurez: del experimental que aún puede cambiar de API a la pieza estable con pruebas, tipos y documentación pulidos. No es burocracia sino una señal de riesgo que te deja decidir con datos —apoyarte a fondo en una etapa 4, tratar con cautela una etapa 1—. Bajo todos ellos vive @solid-primitives/utils, el paquete de cimientos con los ladrillos comunes que el resto reutiliza; hojearlo es ver la infraestructura sobre la que se levanta la colección entera.

⚠️
No metas un paquete por cada línea que ahorras

El tree-shaking hace baratos los paquetes atómicos, pero cada dependencia sigue teniendo un coste de gobierno: versiones que seguir, etapas de madurez que vigilar, superficie que auditar. Para un createMediaQuery de tres líneas que solo usas en un sitio y que dominas por completo, escribirlo tú puede ser más honesto que sumar una dependencia. Reserva la colección para lo que de verdad tiene esquinas —SSR, casos límite, sincronización— y no para envolver un addEventListener trivial que entiendes al dedillo.

Un framework reactivo vive o muere por su ecosistema de primitivos, y saber cuándo reutilizarlo es criterio, no pereza

El nivel entero ha girado en torno a una sola idea: la reactividad de grano fino no termina en las primitivas que la librería trae de fábrica, sino que es un lenguaje con el que se compone un vocabulario sin fin. solid-primitives es lo que ocurre cuando una comunidad toma en serio esa idea y decide no dejar que cada equipo redescubra por su cuenta cómo envolver un matchMedia sin filtrar listeners, cómo antirebotar sin romper la limpieza, cómo persistir sin explotar en SSR. La colección industrializa el molde de la lección anterior y le añade lo que un individuo rara vez sostiene solo: pruebas, etapas de madurez que comunican riesgo, guardas de servidor, tree-shaking que hace gratis la granularidad, y una convención de nombres —create para lo reactivo con dueño, make para lo imperativo con baja— que enseña la teoría con solo leer un import. Pero la lección más profunda no es “usa siempre la colección”, sino que decidir entre reutilizar y fabricar es un acto de ingeniería, no de pereza ni de orgullo. Reutilizar la mercancía común —lo que todo el mundo necesita y nadie debería reescribir— libera tu energía para construir lo que de verdad te distingue, que es justo donde un primitivo hecho a medida, con conocimiento de tu dominio, vale más que cualquier paquete genérico. Quien entiende esto deja de oscilar entre los dos extremos estériles —el que reinventa todo por desconfianza y el que instala todo por comodidad— y se instala en el criterio: leer la colección para aprender el canon, reutilizarla donde aporta esquinas ya resueltas, y fabricar donde el dominio manda. Ese criterio, y no el dominio de ninguna API concreta, es lo que convierte integrar librerías externas en un oficio en vez de una serie de trucos.

⚔️ Elige entre reutilizar y fabricar con criterio
  1. Instala @solid-primitives/media y reemplaza tu createMediaQuery casero por el oficial; confirma que sobrevive a un render de SSR sin reventar.
  2. Usa createEventListener y makeEventListener del mismo paquete y explica, con tu caso, por qué elegiste la variante create o la make.
  3. Sustituye un createStoredSignal propio por makePersisted y comprueba que la hidratación queda contemplada sin que tú la programes.
  4. Antirebota una búsqueda con debounce de @solid-primitives/scheduled y verifica que la limpieza corre al desmontar sin que tú registres nada.
  5. Elige una fuente propia de tu dominio que ningún paquete cubra, fabrica su primitivo con el molde del capítulo anterior, y justifica por qué aquí construir gana a reutilizar.