# React

> @colis/react : envoyer un fichier, retrouver un colis par son code, et un champ de saisie qui corrige le code pendant la frappe.

Canonical: https://colis-docs.vercel.app/docs/react · Markdown: https://colis-docs.vercel.app/docs/react.md

`@colis/react` sert à construire votre propre interface d’envoi ou de retrait : un espace client
dans votre app, un formulaire de dépôt sur votre site. La page de livraison s’en sert pour son champ
« Vous avez déjà un code ? ».

Le paquet ne voit jamais un identifiant de stockage et n’embarque aucun client S3 : il dépend de
`@colis/protocol` et de nanoid, avec React en dépendance pair. Chaque export est un hook client, et le
build porte `'use client'`, donc il s’utilise tel quel dans l’App Router.

## Le fournisseur

Pointez-le vers les routes du [protocole](https://colis-docs.vercel.app/docs/protocole), par exemple celles de votre page de
livraison :

```tsx title="app/providers.tsx"
'use client'

import { ColisProvider } from '@colis/react'

export function Providers({ children }: { children: React.ReactNode }) {
  return <ColisProvider baseUrl="/api/transfers">{children}</ColisProvider>
}
```

`headers` ajoute un en-tête à chaque requête, par exemple le `DROP_PASSWORD` en jeton Bearer. `client`
remplace le client entier : c’est ainsi qu’on pilote les hooks en test, sans réseau.

## Envoyer un fichier

```tsx
import { useSendTransfer } from '@colis/react'

function EnvoyerUnLivrable() {
  const { sendFile, transfer, isPending, error } = useSendTransfer()

  return (
    <>
      <input
        type="file"
        disabled={isPending}
        onChange={(event) => event.target.files?.[0] && sendFile(event.target.files[0])}
      />
      {transfer && <p>Code à transmettre au client : {transfer.code}</p>}
      {error && <p role="alert">{error.message}</p>}
    </>
  )
}
```

`sendFile(file, filename?)` garde le nom et le type du `File`. `send(data, { version, device })`
envoie un objet JSON plutôt qu’un fichier.

<Callout>
  Les échecs arrivent dans `error` plutôt que de rejeter : un gestionnaire d’événement n’a pas besoin d’un `try`/`catch`
  autour de chaque appel. L’appel renvoie `null` quand il a échoué.
</Callout>

Les hooks parlent le protocole, rien de plus. Les options de la page de livraison (durée, mot de
passe, message, usage unique) voyagent dans des en-têtes `x-drop-*` que les hooks n’envoient pas : un
colis créé ainsi prend les valeurs par défaut du déploiement. De même, l’accusé de réception et la
réponse du client passent par les routes `/status` et `/verdict`, à appeler avec `fetch` : voir
[Suivi et validation](https://colis-docs.vercel.app/docs/suivi-et-validation#les-routes).

## Retrouver un colis

```tsx
import { useReceiveTransfer, useSyncCodeInput } from '@colis/react'

function RetrouverUnColis() {
  const input = useSyncCodeInput()
  const { load, loadBytes, transfer, notFound, isPending } = useReceiveTransfer()

  return (
    <>
      <input {...input.inputProps} placeholder="K7QP2M4X" />
      <button onClick={() => input.code && load(input.code)} disabled={!input.isComplete || isPending}>
        Chercher
      </button>

      {notFound && <p>Code inconnu ou expiré.</p>}
      {transfer && (
        <p>
          {transfer.filename} · envoyé le {new Date(transfer.createdAt).toLocaleString('fr-FR')}
        </p>
      )}
    </>
  )
}
```

`load(code)` lit les métadonnées ; `loadBytes(code)` télécharge le contenu ; `burn(code)` détruit le
colis. `notFound` distingue un code inconnu ou expiré d’une vraie erreur.

## Le champ de code

`useSyncCodeInput()` corrige dans le navigateur, avant toute requête : séparateurs retirés, casse
unifiée, `O`, `I` et `L` lus comme `0`, `1` et `1` quand l’alphabet le permet.

| Champ        | Contenu                                                            |
| ------------ | ------------------------------------------------------------------ |
| `value`      | Ce que la personne a tapé, intact                                  |
| `code`       | La forme canonique à envoyer, `null` tant qu’elle n’en est pas une |
| `isComplete` | Vrai quand `code` a la longueur configurée                         |
| `error`      | Défini quand un caractère ne peut pas appartenir à l’alphabet      |
| `inputProps` | Les attributs du champ : clavier, saisie automatique, correction   |

Le champ n’est jamais réécrit sous le curseur : ce qui est tapé reste dans `value`, la forme corrigée
à côté. Passez la même forme que le serveur (`useSyncCodeInput({ length, alphabet })`) et le clavier
suit : un alphabet de chiffres donne `inputMode="numeric"`.

## Concurrence

Chaque appel annule le précédent, une réponse tardive d’un appel remplacé est ignorée, et rien n’est
écrit après le démontage. Quelqu’un qui martèle le bouton ne se retrouve pas devant la requête qui a
fini la dernière par hasard.

## Référence

| Hook                   | Renvoie                                                                                              |
| ---------------------- | ---------------------------------------------------------------------------------------------------- |
| `useSendTransfer()`    | `send`, `sendFile`, `transfer`, `status`, `isPending`, `error`, `reset`                              |
| `useReceiveTransfer()` | `load`, `loadBytes`, `burn`, `transfer`, `data`, `notFound`, `status`, `isPending`, `error`, `reset` |
| `useSyncCodeInput()`   | `value`, `setValue`, `code`, `isComplete`, `error`, `reset`, `inputProps`                            |
| `useTransferClient()`  | Le client sous-jacent, pour ce que les hooks ne couvrent pas                                         |

`status` vaut `'idle' | 'pending' | 'success' | 'error'`. `isTransferError` et `TransferError` sont
réexportés, pour réagir à un code d’erreur sans seconde dépendance.
