# React

> Provider, hooks y el botón hospedado en una aplicación de React.


```bash
npm install @andco/sdk @andco/sdk-react
```

## Montar el provider

```tsx title="src/App.tsx"
import { AndcoProvider } from "@andco/sdk-react";

const clientOptions = {
  clientId: import.meta.env.VITE_ANDCO_CLIENT_ID,
  redirectTo: new URL("/", window.location.origin).href,
  initialScopes: ["openid", "email", "profile"],
} as const;

export function App() {
  return (
    <AndcoProvider clientOptions={clientOptions}>
      <Account />
    </AndcoProvider>
  );
}
```

El provider construye la instancia con un inicializador perezoso. Es seguro en `StrictMode` y bajo Suspense: construir no hace entrada ni salida, y el almacén de sesión solo empieza a cargar cuando algo se suscribe, cosa que React hace al confirmar el render, no al ejecutarlo.

## Leer la sesión

```tsx title="src/Account.tsx"
import { useAndcoSession } from "@andco/sdk-react";

export function Account() {
  const session = useAndcoSession();

  if (session === undefined) return <Skeleton />;
  if (!session) return <SignIn />;
  return <p>{session.user.email}</p>;
}
```

Los tres estados importan. Tratar `undefined` como «sin sesión» hace parpadear la pantalla de ingreso en cada carga.

`useAndcoUser()` devuelve directamente la persona, con los mismos tres estados.

## El botón

```tsx
import { AndcoButton } from "@andco/sdk-react";

<AndcoButton
  authorization={{ scopes: ["openid", "email", "profile"] }}
  onComplete={() => console.log("done")}
  onError={(error) => setMessage(error.message)}
/>;
```

El botón es una superficie de Andco dentro de un iframe que React monta y desmonta. No acepta endpoints, `clientId` ni tema: eso lo toma de la instancia, para que no existan dos copias de un mismo valor que puedan discrepar.

## Autenticar sin el botón

```tsx
import { useAndco } from "@andco/sdk-react";

const andco = useAndco();

await andco.auth.signIn({ scopes: ["openid", "email"] });
await andco.auth.signOut();
```

## Llamar a la API

`andco.rest.http` es un cliente tipado contra el contrato OpenAPI: la ruta se valida al compilar y `data` llega con la forma que declara el esquema.

```tsx
const andco = useAndco();
const [accounts, setAccounts] = useState<{ id: string; name: string }[] | null>(null);

useEffect(() => {
  let alive = true;
  void andco.rest.http.GET("/accounts").then(({ data, error }) => {
    if (alive && !error) setAccounts(data.data);
  });
  return () => {
    alive = false;
  };
}, [andco]);
```

No hay un hook para esto a propósito. Un `useEffect` con el cliente del contexto es tan corto como un hook dedicado y no te obliga a aprender otra API.

Un fallo llega como `error`, nunca lanzado: es un `AndcoAPIError` con `code` para ramificar, `status`, y `response` por si necesitas las cabeceras. Un corte de red produce el mismo `error`, con `status` en `undefined`.

## Con TanStack Query

Cuando ya usas TanStack Query, no hace falta envolver nada. `andco.rest.http` es un cliente de `openapi-fetch`, así que [`openapi-react-query`](https://openapi-ts.dev/openapi-react-query) lo toma tal cual y genera hooks tipados desde el mismo contrato:

```bash
npm install @tanstack/react-query openapi-react-query
```

```tsx title="src/Accounts.tsx"
import type { AndCoRestPaths } from "@andco/protocol/transport";
import { useAndco } from "@andco/sdk-react";
import createQueryClient from "openapi-react-query";
import { useMemo } from "react";

export function Accounts() {
  const andco = useAndco();
  // `createQueryClient` es una factory de closures sin caché propia, así que reconstruirla al
  // cambiar la Identidad autorizada no descarta nada: la caché vive en el QueryClient de TanStack.
  const $api = useMemo(() => createQueryClient<AndCoRestPaths>(andco.rest.http), [andco]);
  const accounts = $api.useQuery("get", "/accounts");

  if (accounts.isLoading) return <Skeleton />;
  if (accounts.error) return <p>{accounts.error.message}</p>;
  return <List accounts={accounts.data.data} />;
}
```

`AndCoRestPaths` es obligatorio: TypeScript no puede deducir el mapa de rutas a partir de las firmas genéricas de un cliente, así que hay que nombrarlo.

El `useMemo` tampoco es opcional, pero fíjate en la dependencia: `andco.rest`, no `andco`. `useAndco()` devuelve una referencia nueva en cada cambio de Identidad Autorizada — inicio de sesión, cierre de sesión, un Grant ampliado — mientras que `rest` es la misma superficie antes y después de iniciar sesión. Memorizar sobre `andco` reconstruiría el cliente de consultas en cada inicio de sesión sin necesidad.

El `error` del hook viene tipado desde el contrato — `error`, `required_scope`, `message`. En ejecución es un `AndcoAPIError`; si necesitas `code`, `status` o `response`, redúcelo con `error instanceof AndcoAPIError`.

Monta `QueryClientProvider` por fuera de `AndcoProvider`. La caché es asunto de tu aplicación, no del SDK, y anidarla adentro sugiere que Andco la exige.

## Mostrar un fallo del callback

```tsx
useEffect(() => {
  void andco.session.ready.then(({ error }) => {
    if (error) setMessage(error.message);
  });
}, [andco]);
```

Una vuelta de callback que falla ocurre antes de que tu código llame a nada. Sin esto se ve igual que no tener sesión.
