Anidar y componer control flow con For
El control de flujo de Solid compone: Show dentro de Switch, Switch dentro de For, Show envolviendo un For para el estado vacio. Cada nodo posee su subarbol y la destruccion cae en cascada. Patrones y heuristicas de legibilidad.
Show, Switch y For no son sentencias que se ejecutan y terminan: son nodos del grafo que producen subárboles y los poseen. Por eso componen como componen las expresiones de un lenguaje —anidándose, envolviéndose, encadenándose— y su composición hereda una propiedad valiosísima: cuando un nodo padre se desmonta, la destrucción cae en cascada sobre todos sus hijos de control de flujo, con sus efectos y sus limpiezas. Esta lección es sobre combinar esas piezas —sobre todo For— sin que el JSX se convierta en un jeroglífico.
- Anidar
Show,SwitchyForentendiendo la jerarquía de ámbitos. - Ver por qué la destrucción de un padre limpia a sus hijos en cascada.
- Dominar el patrón
Show+Forpara el estado vacío y el condicional por elemento. - Aplicar heurísticas para mantener el control de flujo anidado legible.
Anidar es componer ámbitos
Cada componente de control de flujo abre un ámbito reactivo —un owner— que posee lo que hay dentro. Anidarlos crea una jerarquía de propiedad: el nodo externo decide si existe el interno, y el interno solo cobra vida cuando su rama está activa. Esto no es solo estético; es gestión de recursos gratuita:
<Switch fallback={<NoAutorizado />}>
<Match when={sesion()}>
<Show when={sesion()!.esAdmin} fallback={<PanelUsuario />}>
<PanelAdmin />
</Show>
</Match>
</Switch>
El Show interno no existe hasta que el Match de sesion() gana. Sus efectos no corren, su DOM no se crea, nada de su interior consume recursos mientras no haya sesión. Cuando sesion() pasa a nulo, el Match se desmonta y arrastra consigo al Show y a todo lo que colgaba de él. No escribes una sola comprobación defensiva: la jerarquía de ámbitos la hace por ti.
flowchart TD F[For sobre pedidos] --> R[fila por pedido] R --> SW[Switch por estado del pedido] SW --> B1[Match enviado] SW --> B2[Match pendiente] B2 --> SH[Show si es urgente] SH --> AV[aviso urgente] R -->|el pedido sale de la lista| DIS[For desmonta la fila] DIS --> CAS[cascada limpia Switch Show y efectos] style DIS fill:#f38ba8,color:#11111b style CAS fill:#fab387,color:#11111b
For + Show: estado vacío y condicional por elemento
For es control de flujo tanto como Show: recorrer una colección es ramificar sobre “cuántos y cuáles”. Se combinan en dos patrones canónicos que conviene tener en los dedos. El primero, el estado vacío: envuelve el For en un Show cuyo when es la longitud y cuyo fallback es el mensaje de lista vacía:
import { For, Show } from "solid-js";
<Show when={pedidos().length} fallback={<p>Sin pedidos todavia</p>}>
<ul>
<For each={pedidos()}>
{(pedido) => <li>{pedido.titulo}</li>}
</For>
</ul>
</Show>
Gracias a la coerción de Show, pedidos().length igual a 0 activa el fallback sin fugas de 0 al DOM —justo la trampa de la lección anterior—. El segundo patrón, el condicional por elemento: un Show o un Switch dentro del hijo-función del For, de modo que cada fila decide su propia forma:
<For each={pedidos()}>
{(pedido) => (
<li>
{pedido.titulo}
<Show when={pedido.urgente}>
<span class="urgente">prioritario</span>
</Show>
</li>
)}
</For>
Como For reconcilia por referencia del elemento, cada fila tiene su propio ámbito estable; el Show de dentro vive y muere con esa fila concreta. Reordenar la lista mueve los nodos ya creados sin recrear los Show internos, y eliminar un pedido limpia su fila entera en cascada. Si el estado por posición importara más que la identidad —campos de formulario, por ejemplo—, Index en lugar de For daría a cada posición un ámbito fijo con el elemento como señal.
Las tres piezas encajan en una sola composición legible: estado vacío por fuera, iteración en medio, rama por fila dentro.
<Show when={pedidos().length} fallback={<Vacio />}>
<ul>
<For each={pedidos()}>
{(pedido) => (
<li>
<Switch fallback={<span>estado desconocido</span>}>
<Match when={pedido.estado === "enviado"}><Enviado /></Match>
<Match when={pedido.estado === "pendiente"}><Pendiente /></Match>
</Switch>
</li>
)}
</For>
</ul>
</Show>
Léelo de fuera adentro y aparece el enunciado del problema tal cual: “si hay pedidos, por cada pedido, según su estado”. La forma del JSX es la forma de la frase, y esa coincidencia es la que hace mantenible el anidamiento.
¿Show por fuera del For o For por fuera del Show? Depende de qué gobierna a qué. Estado vacío de toda la lista: Show fuera, para no montar el For en balde. Decisión por elemento: For fuera, Show dentro de cada fila. Ponerlo al revés —un For que siempre corre para luego esconder cada fila con un Show— crea y descarta nodos que nunca debieron nacer. El anidamiento correcto es el que hace que el trabajo caro dependa de la condición que lo justifica.
Legibilidad: aplanar, extraer, nombrar
El control de flujo anidado degenera en pirámide con facilidad. Tres heurísticas lo evitan:
Extrae ramas a componentes
Cuando una rama de Match o el hijo de un Show crece más de unas líneas, dale nombre: <Match when={...}><VistaError error={err()} /></Match>. El Switch queda como un índice legible de estados, y cada vista se lee y se prueba por separado.
Prefiere fallback a otra rama
El caso por defecto va al fallback, no a un Match final con la condición negada de todos los demás. Menos condiciones que mantener sincronizadas, menos formas de que se solapen.
Memoiza el when caro o repetido
Si un when calcula algo costoso o se repite en varias ramas, elévalo a un createMemo con nombre y úsalo por su accesor. La condición gana nombre, se calcula una vez y el JSX se aligera.
La meta no es minimizar líneas, sino que la estructura del JSX refleje la estructura del problema: una lista con estado vacío se lee como un Show sobre un For; una máquina de estados se lee como un Switch de estados nombrados; una fila con adornos condicionales se lee como un For con Show internos. Cuando la forma coincide, el anidamiento deja de pesar porque cada nivel dice una cosa y solo una.
La extracción a componentes es la palanca más potente. Compara una rama gorda inline con su versión con nombre:
// antes: la rama entierra la estructura del Switch bajo detalle visual
<Switch>
<Match when={estado() === "error"}>
<div class="error"><Icono /><p>{mensaje()}</p><button>reintentar</button></div>
</Match>
</Switch>
// despues: el Switch se lee como un indice de estados nombrados
<Switch>
<Match when={estado() === "error"}><VistaError mensaje={mensaje()} /></Match>
</Switch>
El Switch de la derecha se abarca de un vistazo: es un mapa de estados, no una maraña de div. Y VistaError se prueba y se reutiliza por su cuenta. La regla: el control de flujo debe mostrar qué ramas hay; el detalle de cómo se ve cada una vive en el componente de la rama.
La idea que eleva todo este nivel es que, en Solid, anidar control de flujo no es anidar sintaxis: es anidar tiempos de vida. Cada Show, cada Match, cada fila de un For define un ámbito cuya existencia está condicionada por su padre, y dentro del cual viven señales, efectos, recursos y nodos DOM que nacen cuando el ámbito nace y mueren cuando muere. Anidar dos condicionales no es escribir un if dentro de otro if; es declarar que la vida del subárbol interno está contenida en la del externo, con todo lo que eso implica: el interno no consume nada mientras el externo no lo justifique, y cuando el externo cae, el interno cae con él sin que tengas que acordarte de limpiarlo. Esta contención es la misma propiedad que hace seguros a los onCleanup, a las suscripciones y a los recursos asíncronos que aún no hemos visto: todos cuelgan de un owner, y los owners se anidan siguiendo exactamente el anidamiento de tu control de flujo. Por eso combinar Show, Switch y For con criterio no es un ejercicio de estilo, sino de arquitectura: estás dibujando el árbol de vidas de tu interfaz, y ese árbol es el que Solid recorre para crear y destruir con precisión quirúrgica. Piensa en ámbitos, no en indentación, y el anidamiento se vuelve tu aliado en vez de tu deuda.
- Construye una lista con estado vacío:
Showconfallbackenvolviendo unFor. Vacía la colección y confirma que aparece el mensaje sin ningún0fantasma. - Añade un
Switchdentro de cada fila que ramifique por el estado del elemento, y unShowinterno para un adorno condicional. - Elimina un elemento de la colección y demuestra, con un
onCleanupque loguee, que la fila y todos sus condicionales internos se limpian en cascada. - Refactoriza: extrae la rama más grande del
Switcha un componente con nombre y eleva unwhencaro a uncreateMemo. - Invierte a propósito el anidamiento estado-vacío (
Forpor fuera,Showpor dentro) y explica qué trabajo de más provoca.