Saltar al contenido

createAsync frente a createResource: sin tupla, se lee llamando, Suspense por defecto

El mapa exacto de diferencias entre createResource y createAsync: la tupla con mutate y refetch frente al accessor desnudo, los estados loading y error como propiedades frente a las fronteras Suspense y ErrorBoundary, la fuente explícita frente al rastreo automático como en un memo, y la mutación optimista imperativa frente al modelo de revalidación por query. Cuándo cada uno sigue siendo la herramienta correcta y por qué createAsyncStore hereda el patrón de reconcile del nivel de estado.

La tupla que desaparece

createResource devuelve una tupla: un accessor de datos enriquecido con loading, error y latest, más un objeto con mutate y refetch. Es un objeto-panel: todo lo que puedes saber y hacer con la petición cuelga de él.

TSX
import { createResource } from "solid-js";const [usuario, { mutate, refetch }] = createResource(  () => props.id,          // fuente explicita  (id) => getUsuario(id)   // fetcher que recibe la fuente);usuario();          // el valor, o undefined mientras cargausuario.loading;    // boolean de cargausuario.error;      // el error si la promesa rechazousuario.latest;     // ultimo valor durante una recarga

createAsync colapsa todo eso en un accessor con una sola propiedad extra, latest. No hay loading, no hay error, no hay mutate ni refetch colgando del valor: la carga se declara con Suspense, el error con ErrorBoundary, y la revalidación la gobierna el query que lees.

TSX
import { createAsync } from "@solidjs/router";const usuario = createAsync(() => getUsuario(props.id));usuario();          // el valor; SUSPENDE si aun no estausuario.latest;     // ultimo valor sin suspender// loading  -> lo maneja <Suspense>// error    -> lo maneja <ErrorBoundary>// refetch  -> revalidate(getUsuario.key) del router

La reducción no es cosmética. Con la tupla, cada componente que consume el recurso carga con la responsabilidad de comprobar loading y error y de pintar undefined con guardias. Con el accessor desnudo, esa responsabilidad sube a una frontera y desaparece del punto de uso: lees un valor que, dentro del Suspense, siempre está.

De propiedades de estado a fronteras declarativas

El cambio más profundo es dónde vive el estado de la petición. En createResource vive en el valor: preguntas usuario.loading allí donde lo necesites, y el spinner es una rama del render de ese componente. En createAsync vive en el árbol: la carga es una frontera Suspense que envuelve a uno o muchos consumidores, y el error una frontera ErrorBoundary. Un mismo Suspense coordina la carga de varias derivaciones a la vez; un mismo ErrorBoundary recoge el fallo de todas.

TSX
// createResource: el estado se comprueba en cada punto de uso<Show when={!usuario.loading} fallback={<Spinner />}>  <Show when={!usuario.error} fallback={<Error err={usuario.error} />}>    <h1>{usuario()!.nombre}</h1>  </Show></Show>// createAsync: el estado son fronteras que envuelven, no ramas que comprueban<ErrorBoundary fallback={(err) => <Error err={err} />}>  <Suspense fallback={<Spinner />}>    <h1>{usuario().nombre}</h1>  </Suspense></ErrorBoundary>

Compara las dos columnas: la primera dispersa la lógica de estado por cada lectura; la segunda la concentra en dos fronteras y deja el cuerpo limpio. Esta es la misma inversión que Solid hizo con la reactividad —del control imperativo a la declaración— aplicada ahora a lo asíncrono.

Los tres canales de estado quedan así repartidos:

  • Carga que antes leías en usuario.loading ahora la declara un Suspense que envuelve a los consumidores.
  • Error que antes leías en usuario.error ahora lo recoge un ErrorBoundary en la frontera.
  • Valor obsoleto que antes vivía en usuario.latest sigue en latest, la única propiedad que sobrevive en el accessor.

Cuándo cada uno sigue siendo el correcto

createAsync es el nuevo idioma por defecto para leer estado del servidor, pero createResource no se jubila. Elige el recurso cuando necesites sus controles imperativos: mutate para una actualización optimista inmediata antes de que la red confirme, o refetch para forzar una recarga puntual sin pasar por el sistema de query. El recurso te da los mandos en la mano; la derivación te da un modelo declarativo donde la revalidación la orquesta el router.

Y cuando quieras lo mejor de dos mundos —lectura como signal y grano fino sobre colecciones—, Solid Router ofrece createAsyncStore, que envuelve el mismo modelo pero reconcilia cada respuesta contra un store interno. Es la industrialización del patrón que ya montaste a mano en el nivel de estado: recurso para el ciclo, store para el grano fino, reconcile para coser cada instantánea preservando identidad. Con createAsyncStore una lista revalidada no remonta las filas que no cambiaron.

TSX
import { createAsyncStore } from "@solidjs/router";// se lee como createAsync, pero por dentro reconcilia: grano fino en coleccionesconst todos = createAsyncStore(() => getTodos(), { reconcile: { key: "id" } });// <For each={todos()}> no remonta filas intactas al revalidar

Y donde createResource te ofrecía refetch, createAsync delega la recarga en el sistema de query: llamas a revalidate con la clave de la consulta —o dejas que una action la invalide sola tras una mutación— y todas las derivaciones que la leen se recalculan a la vez. La imperatividad de recargar no desaparece, se centraliza en la caché en lugar de colgar de cada valor, y eso es justo lo que permite que una escritura invalide de golpe cuantas vistas dependan del dato.

TSX
import { revalidate } from "@solidjs/router";// fuerza la recarga de una query; toda createAsync que la lea se actualizaawait revalidate(getUsuario.keyFor(props.id));// o revalidate(getUsuario.key) para invalidar todas sus variantes