# Hooks React

> @s3nd/react : des hooks pour envoyer un fichier ou un snapshot, relire un code, et un champ de code de synchronisation qui répare ce que l’utilisateur a tapé. Ne voit jamais un identifiant de stockage, n’embarque jamais le SDK AWS dans votre bundle.

Canonical: https://s3nd.sh/fr/react · Markdown: https://s3nd.sh/fr/react.md · English: https://s3nd.sh/react

Envoyez un fichier ou un snapshot, relisez un code, et un champ qui répare le code au fil de la frappe. Tout son arbre de dépendances, c’est le package protocol et nanoid, avec React en peer. Le SDK AWS reste sur votre serveur.

```sh
npm install @s3nd/react
```
**pointez-le sur les routes de transfert**
```tsx
import { S3ndProvider } from '@s3nd/react'

export default function Providers({ children }) {
  return <S3ndProvider baseUrl="/api/transfers">{children}</S3ndProvider>
}
```

## Un champ de fichier, un code.
sendFile() prend un File directement depuis un input, en gardant son nom et son type. Les échecs atterrissent dans error au lieu de rejeter, parce qu’un gestionnaire d’événement ne devrait pas avoir besoin d’un try/catch.

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

function DropFile() {
  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>Read this out to them: {transfer.code}</p>}
      {error && <p>{error.message}</p>}
    </>
  )
}
```

Le hook poste vers les routes de transfert de votre serveur, qui détient les identifiants du bucket. Le navigateur ne voit jamais une clé, et votre fonction `authorize` décide qui peut envoyer.

`transfer` porte le code, le type, la taille et l’expiration. Affichez le code groupé par quatre ; le côté récepteur l’accepte avec ou sans les espaces.

## Chercher, montrer, puis télécharger.
load() récupère ce qu’un code contient sans déplacer les octets, donc l’utilisateur voit un nom de fichier et une taille avant tout téléchargement. loadBytes() rapatrie le fichier.

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

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

  async function download() {
    const bytes = await loadBytes(input.code!)
    if (bytes) saveToDisk(new Blob([bytes]), transfer?.filename ?? 'file') // your helper
  }

  return (
    <>
      <input {...input.inputProps} placeholder="K7QP 2M4X" />
      <button onClick={() => load(input.code!)} disabled={!input.isComplete || isPending}>
        Look it up
      </button>
      {notFound && <p>Unknown or expired code.</p>}
      {transfer?.kind === 'file' && (
        <button onClick={download}>
          Download {transfer.filename} · {transfer.size} bytes
        </button>
      )}
    </>
  )
}
```

## Ce que l’utilisateur a tapé reste intact.
useSyncCodeInput fait la réparation dans le navigateur, avant toute requête. Réécrire le champ sous le curseur est la chose qui rend ces champs pénibles, donc il ne le fait jamais.

`value` est mot pour mot. `code` est la forme canonique à soumettre, `null` tant que ce qui est tapé ne peut pas en être une. `isComplete` est le moment d’activer le bouton.

`inputProps` porte les indications de clavier et de remplissage automatique qu’un code à usage unique veut : `autoComplete="one-time-code"`, majuscules, pas d’autocorrection, et un clavier numérique quand l’alphabet est fait de chiffres.

Passez la même forme que celle configurée sur votre serveur, `{ length: 4, alphabet }`, et les deux moitiés suivent.

## Les mêmes hooks transportent un snapshot.
L’état structuré passe par send() comme un snapshot, et revient en ligne dans data. Charger et appliquer sont délibérément séparés : seul votre code connaît ses object stores, et l’utilisateur doit voir ce qui va remplacer ses données.

```ts
const { send, transfer } = useSendTransfer()

// Structured state goes as a snapshot, with your schema version.
await send(await exportDatabase(), { version: 3 })

// On the other device: load, show, then apply.
const { load, transfer, data } = useReceiveTransfer<DatabaseDump>()
await load(code)
// transfer.device, transfer.createdAt → show them
// importDatabase(data!) → only after the user confirms
```

L’exemple IndexedDB du dépôt contient une paire export/import complète contre un vrai object store, et la page de cas d’usage déroule tout le flux.

## Un utilisateur qui martèle un bouton obtient une seule réponse.
Chaque appel annule le précédent, une réponse tardive d’un appel remplacé est ignorée plutôt que publiée, et rien n’est écrit après le démontage.

- **Hooks client, prêts pour l’App Router** — Chaque export est un hook client et le build porte `'use client'`, donc ça s’insère directement dans l’App Router de Next.js. React 18 ou plus.
- **Jetons et clients personnalisés** — Passez `headers` au provider pour un jeton, ou `client` pour apporter le vôtre, ce qui est aussi la façon de le piloter en test sans aucun réseau.
- **Un statut que vous pouvez afficher** — `status` vaut idle, pending, success ou error, et `notFound` couvre à la fois un code inconnu et un code expiré, comme le protocole.

- `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 tout ce que les hooks ne couvrent pas
