# React Experto — Patrones y Ejemplos Completos

Referencia extendida de la skill `react-experto`. Contiene ejemplos completos
de data fetching, state management, performance patterns y Next.js App Router.

---

## React Query — Ejemplo completo

```tsx
"use client";
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export function ListaUsuarios() {
  const { data, isPending, error } = useQuery({
    queryKey: ['usuarios'],
    queryFn: () => fetch('/api/usuarios').then(r => r.json()),
    staleTime: 5 * 60 * 1000,  // 5 minutos
  });

  if (isPending) return <Skeleton />;
  if (error) return <ErrorMessage error={error} />;

  return <ul>{data.map(u => <li key={u.id}>{u.nombre}</li>)}</ul>;
}

// Mutacion con invalidacion de cache
export function CrearUsuario() {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: (nuevoUsuario) =>
      fetch('/api/usuarios', {
        method: 'POST',
        body: JSON.stringify(nuevoUsuario),
      }).then(r => r.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['usuarios'] });
    },
  });

  return (
    <button onClick={() => mutation.mutate({ nombre: 'Ana' })}>
      Crear usuario
    </button>
  );
}
```

---

## Server Actions — Formularios y mutaciones desde RSC

```tsx
// app/actions/usuario.ts
"use server";
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
import { z } from 'zod';

const UsuarioSchema = z.object({
  nombre: z.string().min(2),
  email: z.string().email(),
});

export async function crearUsuario(formData: FormData) {
  const resultado = UsuarioSchema.safeParse({
    nombre: formData.get('nombre'),
    email: formData.get('email'),
  });

  if (!resultado.success) {
    return { error: resultado.error.flatten() };
  }

  await db.usuario.create({ data: resultado.data });
  revalidatePath('/usuarios');
  return { success: true };
}

// Uso en un form (Server Component)
export default function FormularioUsuario() {
  return (
    <form action={crearUsuario}>
      <input name="nombre" required />
      <input name="email" type="email" required />
      <button type="submit">Crear</button>
    </form>
  );
}
```

---

## State Management — Zustand

```typescript
// lib/store/usuario.store.ts
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

interface UsuarioStore {
  usuarios: Usuario[];
  filtro: string;
  setFiltro: (filtro: string) => void;
  agregarUsuario: (usuario: Usuario) => void;
}

export const useUsuarioStore = create<UsuarioStore>()(
  immer((set) => ({
    usuarios: [],
    filtro: '',
    setFiltro: (filtro) => set(state => { state.filtro = filtro; }),
    agregarUsuario: (usuario) => set(state => {
      state.usuarios.push(usuario);
    }),
  }))
);

// Selectores especificos para evitar re-renders
export const useFiltroDatos = () =>
  useUsuarioStore(state => state.filtro);
```

---

## State Management — Jotai

```typescript
// lib/atoms/filtros.ts
import { atom, useAtom, useAtomValue } from 'jotai';

const filtroBaseAtom = atom('');
const usuariosAtom = atom<Usuario[]>([]);

// Atomo derivado (computed)
const usuariosFiltradosAtom = atom(get => {
  const filtro = get(filtroBaseAtom).toLowerCase();
  return get(usuariosAtom).filter(u =>
    u.nombre.toLowerCase().includes(filtro)
  );
});

// Uso
function ListaFiltrada() {
  const usuariosFiltrados = useAtomValue(usuariosFiltradosAtom);
  return <ul>{usuariosFiltrados.map(...)}</ul>;
}
```

---

## Next.js App Router — Estructura de directorios

```
app/
+-- layout.tsx           # Layout raiz (Server Component)
+-- page.tsx             # Home
+-- loading.tsx          # UI de carga automatica (Suspense)
+-- error.tsx            # Manejo de errores ('use client')
+-- not-found.tsx        # 404
+-- (auth)/              # Route Group — no afecta URL
|   +-- login/page.tsx
|   +-- registro/page.tsx
+-- dashboard/
|   +-- layout.tsx       # Layout anidado
|   +-- page.tsx
|   +-- [id]/
|       +-- page.tsx     # Ruta dinamica
+-- api/
    +-- usuarios/
        +-- route.ts     # Route Handler
```

---

## Route Handlers (API routes)

```typescript
// app/api/usuarios/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET(req: NextRequest) {
  const { searchParams } = new URL(req.url);
  const pagina = Number(searchParams.get('pagina') ?? '1');

  const usuarios = await db.usuario.findMany({
    skip: (pagina - 1) * 20,
    take: 20,
  });

  return NextResponse.json({ items: usuarios, pagina });
}

export async function POST(req: NextRequest) {
  const body = await req.json();
  const usuario = await db.usuario.create({ data: body });
  return NextResponse.json(usuario, { status: 201 });
}
```

---

## Metadata dinamica

```tsx
// app/usuarios/[id]/page.tsx
export async function generateMetadata({ params }: Props) {
  const usuario = await getUsuario(params.id);
  return {
    title: `${usuario.nombre} — Mi App`,
    description: `Perfil de ${usuario.nombre}`,
  };
}
```

---

## Streaming con Suspense (Next.js App Router)

```tsx
// app/dashboard/page.tsx
import { Suspense } from 'react';

export default function DashboardPage() {
  return (
    <div>
      <Encabezado />

      <Suspense fallback={<SkeletonGrafica />}>
        <GraficaVentas />
      </Suspense>

      <Suspense fallback={<SkeletonTabla />}>
        <TablaReciente />
      </Suspense>
    </div>
  );
}
```
