# Data States Pattern

Todo componente que consume datos asíncronos DEBE manejar 4 estados.

---

## Los 4 Estados Obligatorios

| Estado | Condición | UI Esperada |
|--------|-----------|-------------|
| **Loading** | `isLoading && !data` | Skeleton o spinner contextual |
| **Error** | `isError` | Mensaje de error + acción de retry |
| **Empty** | `!isLoading && data?.length === 0` | Mensaje informativo + CTA opcional |
| **Success** | `data?.length > 0` | Renderizado normal de datos |

---

## Incorrecto (Solo maneja success)

```tsx
function AccountsList() {
  const { data } = useAccounts();

  // Crash si data es undefined
  // No muestra loading
  // No maneja errores
  return (
    <ul>
      {data.map(account => (
        <li key={account.id}>{account.name}</li>
      ))}
    </ul>
  );
}
```

---

## Correcto (Maneja los 4 estados)

```tsx
import { DAlert, DButton } from '@dynamic-framework/ui-react';
import { LoadingState, EmptyState, ErrorState } from '../components';

function AccountsList() {
  const { data, isLoading, isError, refetch } = useAccounts();

  // 1. Loading
  if (isLoading) {
    return <LoadingState variant="list" items={3} />;
  }

  // 2. Error
  if (isError) {
    return (
      <ErrorState
        message="Error al cargar cuentas"
        onRetry={refetch}
      />
    );
  }

  // 3. Empty
  if (!data?.length) {
    return (
      <EmptyState
        message="No tienes cuentas registradas"
        icon="CreditCard"
      />
    );
  }

  // 4. Success
  return (
    <ul>
      {data.map(account => (
        <li key={account.id}>{account.name}</li>
      ))}
    </ul>
  );
}
```

---

## Usando DataStateWrapper (Recomendado)

Para simplificar, usa el componente `DataStateWrapper`:

```tsx
import { DataStateWrapper } from '../components';

function AccountsList() {
  const { data, isLoading, isError, refetch } = useAccounts();

  return (
    <DataStateWrapper
      isLoading={isLoading}
      isError={isError}
      data={data}
      onRetry={refetch}
      emptyMessage="No tienes cuentas registradas"
      emptyIcon="CreditCard"
      loadingVariant="list"
    >
      {(accounts) => (
        <ul>
          {accounts.map(account => (
            <li key={account.id}>{account.name}</li>
          ))}
        </ul>
      )}
    </DataStateWrapper>
  );
}
```

---

## Componentes de Estado Disponibles

### LoadingState

```tsx
// Variantes disponibles
<LoadingState variant="spinner" />        // Spinner centrado
<LoadingState variant="list" items={5} /> // Skeleton de lista
<LoadingState variant="card" />           // Skeleton de card
<LoadingState variant="table" rows={3} /> // Skeleton de tabla
```

### ErrorState

```tsx
<ErrorState
  message="Descripción del error"   // Requerido
  onRetry={refetch}                 // Opcional - muestra botón retry
  color="danger"                    // Opcional - default: danger
/>
```

### EmptyState

```tsx
<EmptyState
  message="No hay datos"           // Requerido
  icon="FileText"                  // Opcional - icono Lucide
  actionText="Crear nuevo"         // Opcional - texto del CTA
  onAction={handleCreate}          // Opcional - handler del CTA
/>
```

---

## Checklist de Implementación

- [ ] ¿El componente usa un hook que retorna `isLoading`?
  - Sí -> DEBE verificar `isLoading` antes de renderizar datos
- [ ] ¿El hook puede fallar (API call)?
  - Sí -> DEBE verificar `isError` y mostrar ErrorState
- [ ] ¿Los datos son un array?
  - Sí -> DEBE verificar `length === 0` para EmptyState
- [ ] ¿Se muestra feedback visual apropiado para cada estado?

---

## Anti-patrones a Evitar

### Loading genérico sin contexto

```tsx
// Malo: no da contexto de qué está cargando
if (isLoading) return <DSpinner />;
```

### Loading contextual

```tsx
// Bueno: skeleton que refleja la estructura final
if (isLoading) return <LoadingState variant="list" items={3} />;
```

### Error sin acción

```tsx
// Malo: usuario no puede hacer nada
if (isError) return <p>Error</p>;
```

### Error con retry

```tsx
// Bueno: usuario puede reintentar
if (isError) return <ErrorState message="Error al cargar" onRetry={refetch} />;
```

### Empty state invisible

```tsx
// Malo: renderiza nada, usuario confundido
if (!data?.length) return null;
```

### Empty state informativo

```tsx
// Bueno: explica la situación
if (!data?.length) return <EmptyState message="No hay cuentas" />;
```

---

## Error Boundaries

Los Data States manejan errores **esperados** (API falla, datos vacíos). Los Error Boundaries manejan errores **inesperados** (bugs, crashes).

### Diferencia

| Tipo | Causa | Manejo |
|------|-------|--------|
| Error esperado | API retorna error 500 | `isError` -> ErrorState |
| Error inesperado | Bug en código, undefined access | ErrorBoundary -> Fallback UI |

### Uso

```tsx
// App.tsx
<QueryProvider>
  <ErrorBoundary>
    <MainWidget />  {/* Si MainWidget crashea, ErrorBoundary lo captura */}
  </ErrorBoundary>
</QueryProvider>
```

### ErrorBoundary con logging externo

```tsx
<ErrorBoundary
  onError={(error, errorInfo) => {
    // Enviar a Sentry, LogRocket, etc.
    Sentry.captureException(error, { extra: errorInfo });
  }}
>
  <MainWidget />
</ErrorBoundary>
```

Ver `src/components/ErrorBoundary.tsx` para implementación completa.

---

## Ver También

- `modyo://docs/widgets/patterns-tanstack-query`
- `modyo://docs/widgets/patterns-utilities`
- `modyo://docs/widgets/reference-domain-patterns`
