◆ 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.