refetch y mutate: recarga manual y actualización optimista
El segundo elemento de la tupla que devuelve createResource son dos palancas sobre el estado asíncrono. refetch re-ejecuta el fetcher bajo demanda —un botón de actualizar, un intervalo, una invalidación tras escribir— al margen de la fuente, y su argumento llega al fetcher como info.refetching. mutate escribe el valor local del resource sin tocar la red, el setter de su signal interno. Juntas construyen la actualización optimista: reflejar el resultado esperado antes de que el servidor confirme, y revertir si falla.
El segundo elemento: dos acciones sobre el resource
createResource devuelve una tupla. Hasta ahora solo has usado el primer elemento —el accessor con su ciclo de vida—, pero el segundo es un objeto con dos acciones que operan sobre el mismo resource.
const [datos, { refetch, mutate }] = createResource(fuente, fetcher);refetch re-lanza la carga; mutate reescribe el valor. Una habla con el fetcher; la otra lo puentea. Entender esa diferencia —una re-deriva el valor desde su fuente de verdad, la otra lo fija localmente contra ella— es entender todo lo que sigue.
Ambas son funciones estables: no cambian de identidad entre renders —de hecho, en Solid ni siquiera hay renders que las recreen— así que puedes pasarlas a un manejador de eventos, a un componente hijo o a un temporizador sin envolverlas en nada ni preocuparte por dependencias. Pertenecen al resource y viven mientras él viva, lo que las hace cómodas de repartir por tu árbol sin las cautelas que otros modelos exigen alrededor de sus setters.
refetch: recargar sin que la fuente cambie
El fetcher corre solo cuando su fuente cambia. Pero a menudo necesitas recargar sin que nada haya cambiado: un botón de refresco, un setInterval que sondea, una invalidación tras una mutación. Para eso está refetch: re-ejecuta el fetcher con el valor actual de la fuente, cuando tú se lo pides.
const [feed, { refetch }] = createResource(traerFeed);// un boton que recarga a mano<button onClick={() => refetch()}>Actualizar</button>;// o un refresco periodico, atado al ciclo de vidaconst id = setInterval(refetch, 30_000);onCleanup(() => clearInterval(id));Puedes pasarle un argumento a refetch, y ese valor llega al fetcher como info.refetching. Es el canal para decirle a la carga por qué se la invoca —una recarga silenciosa de fondo, una forzada por el usuario— y ramificar en consecuencia.
const [feed, { refetch }] = createResource(async (_fuente, info) => { const silenciosa = info.refetching === "silenciosa"; // ajusta el comportamiento segun el motivo de la recarga return traerFeed({ mostrarSpinner: !silenciosa });});refetch("silenciosa"); // info.refetching sera "silenciosa" en el fetcherCuando llamas a refetch y ya había un valor, el resource pasa a refreshing, no a pending: conserva el dato anterior en latest mientras la recarga viaja, justo el comportamiento que viste en la lección anterior. Esa continuidad es lo que hace del refresco periódico una experiencia agradable en vez de un parpadeo cada treinta segundos: el usuario sigue viendo el feed mientras la versión nueva llega por detrás, y solo se sustituye cuando está lista. refetch, además, devuelve la promesa del fetcher, así que puedes esperarla si necesitas encadenar algo justo después de que la recarga complete.
Un uso muy frecuente de refetch es la invalidación tras escribir: completas un formulario que crea o modifica algo en el servidor y, en lugar de adivinar cómo quedó la lista afectada, llamas a refetch sobre el resource que la carga para que vuelva a pedir la verdad. Es el patrón más honesto —releer en vez de simular— y el más simple de razonar, aunque cueste una ida y vuelta a la red que la mutación optimista, que veremos enseguida, se ahorra a cambio de asumir más responsabilidad.
mutate: escribir el valor sin pedirlo
mutate hace algo radicalmente distinto: escribe directamente el valor del resource, sin llamar al fetcher, sin tocar la red. Es, en esencia, el setter del signal interno del resource, y acepta tanto un valor nuevo como un actualizador que recibe el previo.
const [perfil, { mutate }] = createResource(traerPerfil);// fija el valor local a mano, sin peticion algunamutate({ nombre: "Ada", rol: "admin" });// o transforma el valor previomutate((p) => ({ ...p!, rol: "editor" }));Tras un mutate, el resource queda en ready con el nuevo valor, y todo lo que lo lee reacciona de inmediato. No ha habido red; solo has movido el signal. Ese poder es exactamente lo que necesita la actualización optimista.
Merece la pena detenerse en la variante con actualizador, mutate((previo) => ...), porque es la que usarás casi siempre con datos estructurados. Recibe el valor actual y devuelve el nuevo, igual que el setter de un signal, lo que te permite transformar una lista o un objeto sin releerlo desde fuera del mutate. El ! que aparece en los ejemplos —previo!— reconoce que el valor podría ser undefined si el resource aún no ha resuelto; en un flujo optimista real, sueles saber que ya hay datos porque el usuario está interactuando con ellos, pero conviene tratar ese caso con honestidad en lugar de asumirlo.
No confundas mutate con refetch: mutate no dispara el fetcher ni consulta la fuente, solo escribe el signal. Si lo que quieres es el valor fresco del servidor, ese es trabajo de refetch. El malentendido típico —llamar a mutate esperando que recargue— produce interfaces que muestran datos inventados en lugar de reales; ten siempre presente cuál de las dos palancas re-consulta y cuál solo reescribe.
Como todo setter de Solid, mutate respeta la igualdad del signal subyacente: escribir un valor idéntico al actual no propaga nada. Y también como cualquier setter, cuando le pasas una función la interpreta como actualizador del valor previo, no como el valor en sí; almacenar de verdad una función dentro de un resource exigiría envolverla, un caso tan raro en datos de red que rara vez lo encontrarás, pero que conviene conocer para no llevarte una sorpresa.
La actualización optimista, con reversión
El uso canónico de mutate es la actualización optimista: reflejar en la interfaz el resultado esperado antes de que el servidor lo confirme, para que responda al instante, y deshacerlo si la operación falla.
async function alternarFavorito(articulo: Articulo) { const previo = articulo.favorito; // 1. optimismo: actualiza la UI ya, sin esperar al servidor mutate((lista) => marcar(lista!, articulo.id, !previo)); try { // 2. confirma contra el servidor await fetch(`/api/favorito/${articulo.id}`, { method: "POST" }); } catch { // 3. reversion: deshaz el cambio local si fallo mutate((lista) => marcar(lista!, articulo.id, previo)); }}El patrón tiene tres tiempos: escribes el valor optimista con mutate, lanzas la petición real, y si rechaza vuelves a mutate con el valor anterior. Muchos equipos añaden un cuarto tiempo —un refetch tras el éxito— para reconciliar el estado local con la verdad del servidor, por si otra parte del sistema lo cambió mientras la petición viajaba.
La captura del valor previo antes de mutar es la pieza que hace posible la reversión, y es fácil de olvidar. Si no guardas el estado anterior antes de aplicar el optimismo, no tendrás a qué volver cuando el servidor rechace, y tu «reversión» estará adivinando. Por eso el esqueleto empieza siempre leyendo y reteniendo lo que vas a pisar. En interfaces con muchas mutaciones optimistas concurrentes, este patrón manual se vuelve repetitivo, y ahí es donde entran las abstracciones de más alto nivel de SolidStart —action y useSubmission— que industrializan exactamente este baile; pero todas se apoyan, por debajo, en las mismas dos operaciones que acabas de ver.
Hay una asimetría de responsabilidad que conviene aceptar con los ojos abiertos: la mutación optimista mejora la experiencia a cambio de que tú te encargues de la coherencia. El framework no sabe si tu predicción del resultado era correcta, así que confía en ti para revertir o reconciliar. Ese trato —más control a cambio de más responsabilidad— es el que separa una interfaz que se siente instantánea de una que solo espera, y es una elección deliberada, no un descuido del diseño.