SolidJS
Cheatsheet
◆ Cultiva Dev Studio
✓ v1.8

SolidJS — Referencia Completa
de Primitivos Reactivos

Biblioteca UI sin Virtual DOM. Reactividad de grano fino con signals, effects y memos. Cheatsheet de referencia para migración React → SolidJS en el CRM de Cultiva Dev.

signals effects memos stores resources context SolidStart SSR TypeScript
Primitivos de Reactividad
Mapa completo de primitivos SolidJS
createSignal Estado reactivo atómico. Getter + setter.
createEffect Side effects. Se re-ejecuta con deps.
createMemo Estado derivado. Cacheado automáticamente.
createStore Estado complejo. Objetos y arrays anidados.
createResource Async data fetching con Suspense.
createContext Inyección de dependencias global.
Sin re-renders: Los componentes se ejecutan UNA sola vez. Sólo actualizan los nodos DOM exactos que dependen del signal cambiado. No existe el concepto de "re-render" de React.
Accede signals llamándolos: Siempre count() — nunca count. Destructurar un signal rompe la reactividad.
Estado Reactivo Fundamental
S
createSignal — Estado Atómico
signal
import { createSignal } from "solid-js";

// Getter/setter pair — la base de toda reactividad
const [count, setCount] = createSignal(0);

// Leer: llamar como función
console.log(count()); // 0

// Escribir: pasar valor o updater
setCount(1);
setCount((prev) => prev + 1);

// Signal con objetos (CRM lead)
const [lead, setLead] = createSignal({
  name: "Acme Corp",
  status: "nuevo",
  valor: 12000
});
setLead((prev) => ({ ...prev, status: "calificado" }));

// Uso en JSX — reactivo automáticamente
function Counter() {
  const [n, setN] = createSignal(0);
  return (
    <button onClick={() => setN(n() + 1)}>
      Leads: {n()}  // n() en JSX = suscripción reactiva
    </button>
  );
}
Regla clave: Nunca destructures — const { x } = state rompe la reactividad. Usa siempre state.x o createMemo.
E
createEffect — Side Effects
effect
import { createSignal, createEffect } from "solid-js";

const [query, setQuery] = createSignal("");

// Se ejecuta inmediatamente, luego al cambiar deps
createEffect(() => {
  console.log("Buscando:", query()); // auto-tracking
  document.title = `CRM — ${query()}`;
});

// Con cleanup — WebSocket del dashboard en tiempo real
createEffect(() => {
  const ws = new WebSocket(
    `wss://api.cultivacrm.com/leads/${query()}`
  );
  ws.onmessage = (e) => setLeads(JSON.parse(e.data));

  // Cleanup: se ejecuta antes de reusar el efecto
  return () => ws.close();
});

// Efecto con condición — no rastrear condicionalmente
createEffect(() => {
  const val = query(); // tracking ocurre aquí
  if (val.length > 2) {
    fetchLeads(val); // fuera del tracking
  }
});
El auto-tracking detecta automáticamente qué signals se leen. No hay array de dependencias como en React.
M
createMemo — Estado Derivado
memo
import { createSignal, createMemo } from "solid-js";

// CRM: métricas derivadas del pipeline
const [leads, setLeads] = createSignal([
  { id: 1, valor: 5000, estado: "cerrado" },
  { id: 2, valor: 12000, estado: "activo" },
  { id: 3, valor: 8500, estado: "cerrado" },
]);

// Solo recalcula cuando leads() cambia
const cerrados = createMemo(() =>
  leads().filter((l) => l.estado === "cerrado")
);

const totalCerrado = createMemo(() =>
  cerrados().reduce((s, l) => s + l.valor, 0)
);

const conversion = createMemo(() =>
  ((cerrados().length / leads().length) * 100).toFixed(1)
);

// En JSX — memos son signals también (llamar como fn)
return (
  <div>
    <p>Pipeline cerrado: €{totalCerrado()}
    <p>Conversión: {conversion()}%
  </div>
);
Siempre prefiere createMemo sobre createEffect para valores derivados. El memo está cacheado — si las deps no cambian, devuelve el mismo valor sin recalcular.
∙∙∙
createStore — Estado Complejo
store
import { createStore, produce } from "solid-js/store";

// CRM state — objetos y arrays anidados
const [crm, setCRM] = createStore({
  leads: [] as Lead[],
  loading: false,
  filtros: { estado: "todos", etapa: "pipeline" },
  paginacion: { pagina: 1, totalPaginas: 10 },
});

// Actualizar propiedad anidada — ruta granular
setCRM("loading", true);
setCRM("filtros", "estado", "activo");
setCRM("leads", 0, "valor", 15000); // primer lead

// produce() — mutaciones tipo Immer
setCRM(produce((s) => {
  s.leads.push({
    id: 42, name: "TechStart SL",
    valor: 18500, estado: "nuevo"
  });
  s.loading = false;
  s.paginacion.pagina = 1;
}));

// Actualizar múltiples items con selector
setCRM("leads",
  (l) => l.estado === "nuevo",
  "estado", "calificado"  // batch update
);
Store vs Signal: Usa createStore para objetos/arrays con actualizaciones granulares. Usa createSignal para primitivos y objetos pequeños.
R
createResource — Async Data
resource
import { createSignal, createResource, Suspense } from "solid-js";

async function fetchLeadDetail(id: number) {
  const res = await fetch(`/api/leads/${id}`);
  if (!res.ok) throw new Error("Lead no encontrado");
  return res.json();
}

function LeadDetail() {
  const [leadId, setLeadId] = createSignal(1);

  // Re-fetch automático cuando leadId() cambia
  const [lead, { refetch, mutate }] = createResource(
    leadId, fetchLeadDetail
  );

  return (
    // Suspense integrado — sin lógica manual de loading
    <Suspense fallback={<Spinner />}>
      <Show
        when={!lead.error}
        fallback={<p>Error: {lead.error?.message}</p>}
      >
        <h1>{lead()?.nombre}</h1>
        <p>Valor: €{lead()?.valor}</p>
        <button onClick={refetch}>Actualizar</button>
      </Show>
    </Suspense>
  );
}
States: lead.loading, lead.error, lead(). Llama a mutate() para actualizar optimistamente sin re-fetch.
Control Flow — Show / For / Switch
JSX
import { Show, For, Switch, Match, Index } from "solid-js";

// Show — condicional (no usar ternario con componentes)
<Show
  when={isAdmin()}
  fallback={<ReadOnlyDashboard />}
>
  <AdminPanel />
</Show>

// For — lista con re-ordenamiento eficiente (keyed)
<For each={leads()}>
  {(lead, i) => (
    <LeadRow
      lead={lead}
      index={i()}  // i es signal aquí
    />
  )}
</For>

// Switch — múltiples ramas de estado del CRM
<Switch fallback={<p>Estado desconocido</p>}>
  <Match when={estado() === "cargando"}>
    <Skeleton />
  </Match>
  <Match when={estado() === "error"}>
    <ErrorBanner msg={error()}/>
  </Match>
  <Match when={estado() === "ok"}>
    <PipelineView />
  </Match>
</Switch>

// NUNCA uses .map() en JSX — usa <For>
// ❌  {leads().map(l => <Row lead={l} />)}
// ✓  <For each={leads()}>{l => <Row lead={l} />}</For>
For vs Index: <For> para listas con reordenamiento (CRM leads). <Index> cuando los items cambian en sitio pero no se reordenan (celdas de tabla fija).
Patrones Avanzados — Context y SolidStart SSR
C
createContext — Inyección de Dependencias
context
import {
  createContext, useContext,
  type ParentComponent
} from "solid-js";
import { createStore } from "solid-js/store";

// CRM Auth Context
type AuthCtx = {
  user: () => User | null;
  login: (u: User) => void;
  logout: () => void;
};

const AuthContext = createContext<AuthCtx>();

// Provider — envuelve el árbol de componentes
export const AuthProvider: ParentComponent = (props) => {
  const [state, setState] = createStore<{user: User|null}>(
    { user: null }
  );
  const value: AuthCtx = {
    user: () => state.user,
    login: (u) => setState("user", u),
    logout: () => setState("user", null),
  };
  return (
    <AuthContext.Provider value={value}>
      {props.children}
    </AuthContext.Provider>
  );
};

// Hook para consumir — en cualquier componente hijo
export const useAuth = () => useContext(AuthContext)!;

// Uso en componente
function NavBar() {
  const { user, logout } = useAuth();
  return <p>Hola, {user()?.name}</p>;
}
Mismo API que React useContext. El ! en useContext(Ctx)! asume que siempre hay Provider — añade validación si no es seguro.
SolidStart — Full-Stack SSR
solidstart
// File-based routing: src/routes/leads/[id].tsx
import { createAsync, useParams } from "@solidjs/router";
import { Show, Suspense } from "solid-js";
import { getLead } from "~/lib/leads"; // server fn

export default function LeadPage() {
  const params = useParams();
  const lead = createAsync(() =>
    getLead(Number(params.id))
  );
  return (
    <Suspense fallback={<Skeleton />}>
      <Show when={lead()}>
        {(l) => (
          <article>
            <h1>{l().nombre}</h1>
            <p>Valor: €{l().valor}</p>
          </article>
        )}
      </Show>
    </Suspense>
  );
}

// src/lib/leads.ts — sólo corre en el servidor
"use server";

export async function getLead(id: number) {
  return db.query.leads.findFirst({
    where: eq(leads.id, id)
  }); // Drizzle/Prisma — seguro, no expuesto
}

export async function updateLeadStatus(
  id: number, estado: string
) {
  // Server action — llámala desde cualquier componente
  return db.update(leads).set({ estado })
    .where(eq(leads.id, id));
}
"use server" convierte funciones en RPCs seguros. El cliente nunca ve el código — acceso a DB, env vars y secretos completamente protegido.
Guia de Migración React → SolidJS
Equivalencias directas — React vs SolidJS
React SolidJS Nota clave
useState(0) createSignal(0) Devuelve [getter, setter]. Leer: count(), no count
useEffect(() => {}, [dep]) createEffect(() => { dep(); }) Sin array de deps — auto-tracking por lectura de signals
useMemo(() => calc, [dep]) createMemo(() => calc()) También auto-tracking. El memo es un signal de solo lectura
useReducer createStore Actualizaciones granulares sin re-render del componente entero
useContext(Ctx) useContext(Ctx) API idéntico — createContext también idéntico
React.memo(Component) No necesario SolidJS no re-renderiza componentes — reactividad granular
items.map(i => <Row />) <For each={items()}> For hace diff eficiente. .map() recrea todos los nodos
useRef(null) let el!: HTMLDivElement O createSignal para refs reactivos. let el = asigna en onMount
React.lazy + Suspense lazy() + <Suspense> lazy() de solid-js — mismo patrón, integrado con createResource
Reglas de Oro — Golden Rules SolidJS
Patrones correctos e incorrectos
Llama los signals como función count() en JSX, effects y memos — nunca count sin paréntesis
No destructures signals const { x } = state rompe el tracking. Usa state.x o createMemo
Usa <For> para listas en JSX Nunca .map() — For hace reconciliación eficiente keyed
No esperes re-renders Los componentes corren UNA vez. Toda la reactividad va dentro de JSX, effects o memos
createStore para objetos/arrays complejos Actualizaciones granulares sin clonar todo el estado
No leas signals fuera del tracking scope Leer un signal en setTimeout sin efecto no actualiza la UI
createResource para async con Suspense Integración automática — no manejes loading/error manualmente
createMemo sobre createEffect para derivados El memo está cacheado — el effect es para side effects reales
Rendimiento por defecto: SolidJS es ~10x más rápido que React en benchmarks de listas reactivas. El CRM con 500 leads en tiempo real se beneficia especialmente del DOM virtual eliminado.
SolidStart para el CRM: SSR con server functions ("use server") permite acceso seguro a DB desde componentes. Cero boilerplate de API routes para queries simples.