Claude Code me hacía hooks desastrosos. Así lo solucioné
A medida que fui utilizando Claude Code de forma más autónoma en mis proyectos React, los hooks fueron de los lugares con más acumulación de deuda técnica. Todo funcionaba, pero tocar cualquiera de esos hooks desencadenaba side effects y bugs — producto de las malas prácticas que Claude había ido introduciendo sin supervisión.
Sin guía, Claude me generaba hooks enormes con anti-patterns, ignorando la librería de data fetching (cliente que maneja fetch, cache, revalidación y estado de carga) que ya tenía instalada en el proyecto. TanStack Query configurada y activa — y Claude me hacía un useState + useEffect. Incumplía el principio de responsabilidad única: en el mismo hook metía el fetch de datos, la transformación de la respuesta y los side effects — todo en un archivo de 300 líneas, en vez de separar cada responsabilidad en su propia pieza. Incumplía el principio de reutilización: cuando un helper o función auxiliar ya existía en otro hook del proyecto, en vez de importarla la volvía a escribir de cero. Cada hook nuevo heredaba los mismos vicios, y cada refactor arrastraba regresiones a componentes que ni siquiera sabía que dependían de él.
El problema no era que Claude fuera "malo". El problema era que yo, conversación tras conversación, tenía que volver a explicarle las mismas reglas. "Usá TanStack". "Dividí este archivo". "No hardcodees la key". Cada sesión empezaba de cero.
Primer intento: meterlo en el agente de frontend
Mi primera solución fue la obvia: metí todas las reglas de hooks en el system prompt (la instrucción base que el agente recibe antes de cualquier tarea) de mi agente de frontend, junto con el resto de reglas del proyecto. Este enfoque resolvía el problema, pero no era el más eficiente.
El agente de frontend lo uso para muchas cosas — estilos, componentes, rutas, layouts, accesibilidad, respetar la arquitectura del frontend y los patrones de diseño del proyecto. Cada vez que lo invocaba por algo que no era un hook, estaba cargando en el contexto docenas de reglas sobre key factories, discriminated unions y SSR hydration que no tenían nada que ver con la tarea. Peor: a medida que iba agregando más reglas sobre hooks, ese bloque crecía y crecía, desperdiciando espacio útil que hacía falta para el resto del trabajo.
El enfoque resolvía el problema original, pero a cambio generaba un efecto secundario crítico: me llenaba el context window (el espacio total de tokens que el modelo puede considerar por respuesta), que al fin y al cabo es uno de los recursos más valiosos dentro de un agente de desarrollo. Solucionaba, sí, pero no era la mejor opción.
El insight: cargar las reglas on-demand
El salto mental fue simple una vez que lo ví: esas reglas no tienen por qué vivir en el contexto todo el tiempo. Sólo las necesito cuando estoy tocando hooks. Hay que extraerlas del agente y cargarlas lazy — sólo cuando un trigger las pide.
Ese patrón ya tenía nombre: Skills.
Qué son las Skills
Las skills son instrucciones en Markdown que Claude sigue automáticamente cuando detecta un trigger (una frase, intención o patrón definido en la skill). No son código compilado, no son plugins — son "knowledge packs": archivos .md con frontmatter YAML que definen cuándo activarse y qué patrones seguir.
Cuando instalás una skill (por ejemplo, con un comando tipo npx skills add <autor>/<skill>), se copia a .claude/skills/ en tu proyecto. La próxima vez que le pedís a Claude algo que matchea un trigger (por ejemplo, "creá un hook para buscar usuarios"), la skill se carga y Claude sigue las instrucciones en vez de improvisar.
Las skills no son plugins ni extensiones. Son documentación estructurada que Claude lee y sigue. Markdown puro, cargado sólo cuando hace falta.
De 1 a 4 skills
La primera skill que armé era específica para mi stack principal: React + Next.js + TanStack Query. Funcionó. Los hooks empezaron a salir consistentes.
Después apareció el primer límite. Empecé un proyecto nuevo con SWR en vez de TanStack. La sintaxis es distinta — useSWR en vez de useQuery, strings en vez de arrays como cache keys, trigger() en vez de mutate(). Mi skill de TanStack no servía. Armé una segunda para SWR.
El segundo límite llegó al volver a un proyecto React sin Next.js — sin App Router, sin Server Components, sin 'use client'. Lo que era obligatorio en la skill de Next se convertía en ruido. Dos skills más, esta vez universales.
Terminé con una matriz 2x2:
| TanStack Query | SWR | |
|---|---|---|
| React universal | react-tanstack-hooks | react-swr-hooks |
| Next.js | nextjs-tanstack-hooks | nextjs-swr-hooks |
Cada skill tiene negative triggers (reglas que impiden que una skill se active cuando otra más específica ya matcheó) que previenen conflictos. Si estoy en un proyecto Next.js y pido un hook con SWR, nextjs-swr-hooks se activa y react-swr-hooks se queda en silencio. Esto evita el problema más común al armar skills: que dos se activen al mismo tiempo y generen instrucciones contradictorias.
Los patrones que terminé imponiendo
Las 4 skills comparten un núcleo. Cada regla que terminó adentro nació de un error repetido que estaba cansado de corregir.
Estructura de archivos. Los hooks solían salir como un solo archivo de 300 líneas. Ahora cada hook vive en su propio directorio, con cuatro archivos. Sin excepciones.
| Archivo | Responsabilidad |
|---|---|
hooks/use-hook-name/index.ts | Barrel export (solo re-exporta) |
hooks/use-hook-name/use-hook-name.ts | Implementación del hook |
hooks/use-hook-name/use-hook-name.types.ts | Tipos e interfaces |
hooks/use-hook-name/use-hook-name.test.ts | Tests |
Key factory centralizado. El error clásico: keys hardcodeadas en cada hook. Después, querer invalidar una query y tener que hacer grep por el string. TanStack usa queryKeys con arrays, SWR usa swrKeys con strings. Un solo archivo como fuente de verdad.
// lib/swr-keys.ts
export const swrKeys = {
users: {
all: () => '/api/users' as const,
detail: (id: string) => `/api/users/${id}` as const,
search: (query: string) => `/api/users?q=${query}` as const,
},
posts: {
all: () => '/api/posts' as const,
detail: (id: string) => `/api/posts/${id}` as const,
byUser: (userId: string) => `/api/users/${userId}/posts` as const,
},
} as const;Tipos estrictos. Return types explícitos, discriminated unions (tipos union que se distinguen por un campo como status) para loading/error/success, archivo de tipos separado cuando hay 3+ interfaces. Cero any.
Anti-patterns. Cada skill incluye una tabla de errores comunes con la explicación de por qué son malos y qué hacer en su lugar. No es teoría — son exactamente los errores que me comí antes de armar la skill.
Checklist de verificación. Pasos numerados e imperativos (no checkboxes) que Claude ejecuta antes de entregar el hook. "Verificá que el key factory esté importado". "Confirmá que el return type sea explícito". La diferencia entre que la skill funcione o no está en que Claude los siga uno por uno.
Troubleshooting. Tablas de 5-8 filas con formato fallo – causa – solución. Cuando algo no compila, Claude tiene una referencia en vez de improvisar.
TanStack vs SWR: por qué necesité dos versiones
Las dos librerías resuelven el mismo problema — data fetching con cache — pero con filosofías distintas. Y esas filosofías no se traducen bien entre una y otra.
| Aspecto | TanStack Query | SWR |
|---|---|---|
| API surface | useQuery, useMutation, useInfiniteQuery | useSWR, useSWRMutation, useSWRInfinite |
| Mutations | useMutation con onSuccess/onError | useSWRMutation con trigger() |
| Optimistic updates | Via onMutate + setQueryData | Via optimisticData option |
| Middleware | No (interceptors en el fetcher) | Sí, middleware nativo |
| Cache keys | Arrays: ['users', id] | Strings: '/api/users/${id}' |
| SSR (Next.js) | HydrationBoundary + dehydrate | SWRConfig + fallback |
| DevTools | @tanstack/react-query-devtools | swr-devtools (community) |
La diferencia que más impacta en el día a día son las cache keys. TanStack usa arrays jerárquicos (['users', 'detail', id]) que permiten invalidación granular. SWR usa strings que son las URLs del endpoint, lo que es más simple pero menos flexible para invalidar queries relacionadas. Si tus mutations son simples, SWR alcanza; si necesitás invalidar grupos enteros de queries al cambiar algo, TanStack te salva.
No hay "mejor" librería. TanStack ofrece más control, SWR es más minimalista. La skill se asegura de que uses correctamente la que elegiste.
El factor Next.js
Cuando saqué las variantes Next.js me di cuenta de que no era sólo "React + un par de extras". Hay capas enteras que no existen fuera de Next.
Directiva `'use client'`. Obligatoria en cada archivo de hook. Sin eso, Next intenta ejecutarlo en el servidor y falla silenciosamente. Vi este bug más veces de las que me gustaría admitir.
SSR hydration. Acá es donde más errores ví generados por Claude. Hydration es el proceso donde React en el cliente toma el HTML ya renderizado en el servidor (SSR) y le conecta la interactividad. TanStack usa HydrationBoundary + dehydrate en Server Components para pre-cargar datos que el hook consume en el cliente. SWR usa SWRConfig + fallback + unstable_serialize para el mismo efecto. La skill deja el patrón correcto explícito.
React 19. Las variantes Next.js incluyen patrones para `useActionState`, `useFormStatus` y `useOptimistic` — APIs nuevas que Next ya soporta y que muchas veces son la respuesta correcta antes de llegar a TanStack o SWR.
React Compiler. Acá hay una diferencia clave. El compiler auto-memoiza componentes y hooks, haciendo innecesarios los useMemo y useCallback escritos a mano. Las skills de Next lo imponen de forma absoluta: nada de memoización manual. Las skills universales son condicionales: si el proyecto tiene compiler habilitado, sin memoización manual; si no, con ella donde haga falta.
Auditar proyectos reales
Armar las skills fue la mitad del trabajo. La otra mitad fue aplicarlas a proyectos que ya existían.
Lo que hice fue darle a Claude los directorios hooks/ de mis proyectos y pedirle que los auditara contra la skill recién instalada: qué reglas incumplen, cuáles son las más críticas, qué refactorizar primero. La lista siempre era larga — y priorizar no fue fácil.
Los hooks son una de las partes más sensibles de una app React. Un bug en un hook no se queda ahí: se propaga a cada componente que lo usa. Un useEffect con dependencias mal declaradas puede generar loops infinitos. Un key duplicado en TanStack colisiona en cache y hace que dos hooks lean el mismo slot — uno termina devolviendo datos fetcheados por el otro. Tocar un hook "para mejorarlo" sin red de tests es arriesgado.
Por eso las auditorías se convirtieron en un ejercicio de triaje: qué vale la pena refactorizar ya, qué puede esperar, qué se cambia sólo cuando se toque por otra razón.
La deuda técnica no se cobra cuando la contraés — se cobra meses después, cuando tocás ese código y descubrís todas las capas que dejó atrás.
Un caso real: aplicar la skill a un hook con deuda técnica
La mejor forma de mostrar por qué estas skills importan es con un caso concreto. Hace poco me tocó un bug en desarrollo: una página que no cargaba contenido, las peticiones quedaban colgadas en pending indefinidamente. El hook que manejaba esa página era el candidato perfecto para auditarlo contra la skill.
Estado del hook antes del refactor:
- 550 líneas
- 5
useEffect - 6 responsabilidades mezcladas: fetch principal, fetch de fallback, estado derivado de URL, construcción de UI state, estado de panel y handlers de navegación
fetch()manual +useState+useEffect+AbortControllercasero para cancelar requests en vuelouseMemo/useCallbackescritos a mano sobre un proyecto con React Compiler habilitado- Una función utility duplicada literal en tres hooks distintos
Reglas de la skill que incumplía:
| Regla | Incumplimiento |
|---|---|
Nunca fetch() manual + useState + useEffect para datos | La carga era 100% manual |
| Nunca hardcodear query keys | Los keys se armaban inline en vez de usar el factory centralizado |
| Máximo ~300 líneas por hook | 550 |
Máximo 2 useEffect, ideal 0–1 | 5 |
| Una sola responsabilidad | 6 mezcladas |
No escribir useMemo / useCallback manual — el compiler lo maneja | Varios, enmascarando dependencias inestables |
| No duplicar utilities compartidas | Misma función copiada en 3 hooks |
Causa raíz del síntoma. La función de fetch era un useCallback con dependencias inestables, y el useEffect que lo disparaba dependía además de un flag de loading de otro hook que alternaba varias veces durante la carga inicial. Cada tick abortaba el request anterior y lanzaba uno nuevo. Con el límite de ~6 conexiones concurrentes por host del navegador en HTTP/1.1, y aborts que no liberaban el socket a tiempo, la última petición quedaba en pending sin resolver jamás. El backend respondía en menos de un segundo cuando lo consultaba directo — el cuello estaba en el cliente.
Resultado al aplicar las reglas:
- Orquestador chico de ~320 líneas, 0
useEffect, que sólo compone - Un hook nuevo chico (~80 líneas) aislando la responsabilidad de panel state
- Fetch principal y fallback migrados a TanStack Query (la librería del stack). Beneficios automáticos: deduplicación por key, cache compartido, aborts nativos
- Query keys consumidos desde el factory centralizado, con entradas nuevas cuando hacían falta
- Utility duplicada colapsada a una sola en
lib/utils/ - Diff neto: −504 líneas
Comportamiento antes / después:
- Antes: 5–6 requests en cascada al mismo endpoint, la mayoría abortados, una en
pendingque no resolvía - Después: 1 request, respuesta 200, contenido cargado
La skill no era una lista de estilo cosmético. Cada regla tenía una razón operativa concreta. "No usesfetchmanual conuseEffect" no era dogma: el patrón, aplicado sin cuidado, produce dependencias inestables que degradan el pool de conexiones del navegador hasta hacer la página inusable. El bug y el incumplimiento de la regla eran la misma cosa. Cumplir la regla resolvió el síntoma al atacar la causa.
Cómo instalar y usar
Elegí la skill que corresponda a tu stack:
# TanStack Query
npx skills add amunozdev/react-tanstack-hooks # React universal
npx skills add amunozdev/nextjs-tanstack-hooks # Next.js
# SWR
npx skills add amunozdev/react-swr-hooks # React universal
npx skills add amunozdev/nextjs-swr-hooks # Next.jsUna vez instalada, la skill se activa automáticamente cuando un trigger coincide. Cuando pedís "creá un hook para buscar usuarios con SWR", Claude:
- Crea el directorio
hooks/use-user-search/con los 4 archivos - Importa el key factory centralizado (o lo crea si no existe)
- Define tipos explícitos en el archivo
.types.ts - Implementa el hook con error handling, loading states y return types
- Genera tests básicos
- Ejecuta el checklist de verificación
Setup recomendado. En mis proyectos tengo el agente de frontend configurado para cargar automáticamente la skill de hooks cuando detecta trabajo relacionado — desarrollo nuevo, modificaciones, bugfixes. Así la regla de oro se cumple: las instrucciones sólo están en contexto cuando hacen falta.
Proceso de evaluación
Cada una de las 4 skills se construyó siguiendo la guía oficial de Anthropic para desarrollar skills, y se evaluó contra los checks que la guía recomienda: validez del frontmatter YAML, especificidad de la descripción, calidad de los triggers (positivos y negativos), accionabilidad del contenido, forma imperativa y presencia de troubleshooting. Autoevaluación promedio contra ese rubric: 4.9/5.
La evaluación no es sólo calidad del código. Es calidad de las instrucciones que la IA va a seguir.
Qué queda afuera (por ahora)
El scope actual de las 4 skills cubre sólo data fetching — hooks que traen datos de una API con TanStack Query o SWR. Lo que queda afuera es el manejo de estado con librerías de stores: Zustand, Jotai, Redux y similares. En mis proyectos actuales no las uso — el estado local con hooks de React más los caches de TanStack / SWR me alcanzan para la mayoría de los casos.
Es un candidato claro para expandir a futuro: armar skills específicas para patrones de stores (selectors, slices, acciones, persistencia) con la misma lógica — una por librería, cada una con su sintaxis, cargadas sólo cuando hace falta. Si llega el escenario, sumo variantes a la matriz.
Fuentes
Repos de las skills
Docs y referencias