wandres.dev
LIFECYCLE Y REFS · onMount, ciclo de vida

onMount: el trabajo que espera al DOM

Cuándo corre onMount (una sola vez, tras el render inicial y solo en el cliente), para qué sirve (el nodo ya está adjunto al documento: enfocar, medir, instanciar librerías) y por qué por dentro no es más que un createEffect cuyo cuerpo va envuelto en untrack: un efecto que no rastrea nada y por eso jamás se repite.

⏱ 12 min

Hay una clase de trabajo que no puede vivir en el cuerpo del componente: enfocar un input, medir un elemento, arrancar un gráfico. Todo eso exige que el nodo ya exista y cuelgue de la página, y el cuerpo corre antes de que eso ocurra. onMount es la puerta a ese instante posterior: el momento en que el árbol que devolviste ya está insertado en el documento y el mundo real está por fin disponible. Y aunque se presenta como un “hook de ciclo de vida”, por dentro no es sino un efecto que decidió no rastrear nada.

🎯 Al terminar esta lección sabrás
  • Saber que onMount corre una sola vez, después del render inicial y solo en el cliente.
  • Entender por qué dentro de onMount el nodo ya está insertado en el documento vivo.
  • Reconocer que onMount es un createEffect cuyo cuerpo va envuelto en untrack.
  • Distinguir un “efecto sin dependencias” de un array de deps vacío [] al estilo React.

Un disparo único después del montaje

El cuerpo de un componente Solid corre una sola vez y construye el JSX en memoria; en ese instante los nodos existen como objetos, pero todavía nadie los ha insertado en la página. onMount registra una función que Solid ejecutará después: cuando el árbol devuelto ya está adjunto al documento.

import { onMount } from "solid-js";

function Buscador() {
  let entrada!: HTMLInputElement;
  onMount(() => {
    entrada.focus();          // el nodo ya vive en la pagina
    entrada.select();
  });
  return <input ref={entrada} placeholder="buscar" />;
}

Enfocar exige que el elemento esté en pantalla; medir exige que tenga geometría; ambas cosas solo son ciertas tras la inserción. Por eso todo lo que toca el DOM real va en onMount, no junto a la declaración del ref. Si intentas leer el nodo en el cuerpo, aún no existe:

function Roto() {
  let caja!: HTMLDivElement;
  const ancho = caja.offsetWidth;   // TypeError: caja es undefined aqui
  return <div ref={caja} />;
}

onMount es también el lugar natural para arrancar trabajo asíncrono de inicialización que solo importa en el cliente: cargar datos, pedir permisos, abrir una conexión. Como corre tras el montaje y solo en el navegador, no bloquea el render inicial ni se ejecuta en el servidor.

function Perfil() {
  onMount(async () => {
    const datos = await fetch("/api/perfil").then((r) => r.json());
    aplicar(datos);           // el componente ya esta en pantalla
  });
  return <section>...</section>;
}

onMount corre una vez y nunca más. No es un lugar para reaccionar a cambios de estado —eso es trabajo de los efectos—, sino para el trabajo de arranque que solo tiene sentido cuando el componente acaba de aparecer.

Por qué el DOM ya existe aquí

La clave está en el orden de fases del runtime. Primero corre el cuerpo y se crean los nodos; luego el runtime los inserta en el documento; y solo entonces se vacía la cola de efectos diferidos, entre ellos onMount.

flowchart TD
A[el cuerpo del componente corre una vez] --> B[se construye el JSX en memoria]
B --> C[el runtime inserta el arbol en el documento]
C --> D[se vacia la cola de efectos]
D --> E[onMount corre con el DOM ya adjunto]
style A fill:#89b4fa,color:#11111b
style C fill:#fab387,color:#11111b
style E fill:#a6e3a1,color:#11111b

Esa posición al final de la secuencia es lo que garantiza el DOM. Y también explica dos límites: en SSR, donde no hay documento, onMount no corre en el servidor; solo se dispara en el cliente. Por eso cualquier acceso al nodo —medir, enfocar, observar— debe vivir aquí y no en el cuerpo, que sí se ejecuta en el servidor, donde no hay nodo que capturar.

onMount por dentro: un efecto que no rastrea nada

Aquí está la revelación que ordena todo el nivel. onMount no es una primitiva especial del scheduler: es azúcar sobre createEffect. Su definición completa cabe en una línea.

// Asi lo define Solid, sin magia oculta:
export function onMount(fn: () => void): void {
  createEffect(() => untrack(fn));
}

Un createEffect corriente ya corre después del montaje —como toda la cola de efectos— y se re-ejecuta cuando cambia algún signal que lee. El único añadido de onMount es envolver tu función en untrack: dentro de untrack ninguna lectura reactiva crea una suscripción. Sin suscripciones, no hay nada que dispare un segundo run. El efecto corre una vez y queda inerte.

Por eso “efecto sin dependencias” es literal, no metafórico. En React, useEffect(fn, []) declara un array vacío que una regla de lint vigila y que el runtime compara en cada render; el “no repetir” es una convención sobre datos. En Solid no hay array ni comparación: la ausencia de dependencias es estructural, consecuencia física de que untrack impide que se forme cualquier arista en el grafo.

Ese untrack solo alcanza las lecturas síncronas del cuerpo de onMount. Si dentro creas un createEffect anidado, ese efecto tiene su propio ámbito de rastreo y reacciona con normalidad: la envoltura no se hereda hacia los cómputos hijos. Es justo lo que permite instanciar una librería en onMount y, dentro, registrar efectos que sincronizan props —el patrón que cierra este nivel.

ℹ️
Un createEffect también sirve, onMount solo nombra la intención

Como onMount es un createEffect, un efecto normal que no lea ningún signal se comportaría casi igual: correría una vez tras el montaje. La diferencia es la garantía: untrack asegura que aunque sin querer leas un signal dentro, no crearás una suscripción y no habrá reruns accidentales. onMount documenta “esto es arranque, no reacción” y blinda esa promesa.

onMount frente a otros timings

onMount no es el único punto posterior al cuerpo. Solid distingue los efectos por cuándo corren respecto al pintado del navegador. createRenderEffect corre durante la fase de render, antes de pintar; createEffect —y por tanto onMount— corre después, con el árbol ya insertado. Para medir o enfocar quieres el segundo, porque el nodo debe estar adjunto.

import { createRenderEffect, createEffect } from "solid-js";

createRenderEffect(() => console.log("render: el DOM puede no estar conectado aun"));
createEffect(() => console.log("post-render: el DOM ya esta insertado"));

Un createRenderEffect que mida offsetWidth puede leer cero porque corre antes de la inserción. La regla se mantiene: para tocar geometría, foco u observadores, siempre onMount.

🏗️

Cuerpo

Corre una vez, antes de crear el DOM. Declaras signals y devuelves JSX; el nodo todavía no existe.

🎨

createRenderEffect

Corre durante el render, antes del pintado. Sirve para trabajo previo, no para medir un nodo que quizá no esté conectado.

📌

onMount

Corre una vez tras insertar el árbol. El sitio para enfocar, medir e instanciar librerías que tocan el DOM.

🧹

onCleanup

Corre al desmontar el dueño. La otra mitad simétrica: libera lo que onMount reservó.

onMount no es un momento, es una posición en el grafo

La tentación al venir de otros frameworks es leer onMount como un evento del ciclo de vida —“el navegador avisa de que el componente nació”— y tratarlo como una casilla que rellenas. En Solid no existe tal evento: existe un grafo reactivo con un orden de ejecución, y onMount es simplemente la posición de un efecto dentro de ese orden, más la decisión de no rastrear nada para que no vuelva a correr. Entender esto disuelve preguntas que en otros modelos son difíciles. Por qué el DOM está listo: porque los efectos se vacían después de insertar el árbol, y onMount es un efecto. Por qué corre una vez: porque untrack le niega dependencias. Por qué no corre en el servidor: porque el servidor no ejecuta la cola de efectos. Por qué puedes registrar un onCleanup dentro: porque hereda el mismo dueño que cualquier efecto. No memorices una tabla de cuándo se dispara cada hook; interioriza que en Solid el “ciclo de vida” es una consecuencia emergente de dónde cae cada cómputo en el grafo y de qué decide observar. onMount es el primer y más simple ejemplo: un efecto que corre tarde y mira hacia otro lado.

⚔️ Confirma cuándo y cuántas veces corre
  1. Escribe un componente con un console.log("montado") dentro de onMount y un botón que incremente un signal; confirma que el log aparece una sola vez pese a los clics.
  2. Enfoca automáticamente un input al aparecer el componente usando la variable ref y onMount.
  3. Intenta leer elemento.offsetWidth en el cuerpo del componente, observa el error, y muévelo dentro de onMount para que funcione.
  4. Reescribe tu onMount como un createEffect que no lea ningún signal y comprueba que también corre una sola vez tras el montaje.
  5. Explica en una frase por qué ese efecto no se repite aunque leyeras un signal dentro de un onMount.