# React hooks

> @colis/react: hooks to put sending and pickup inside your own app or client portal, with a code input that repairs what the client typed. Never sees a storage credential, never pulls the AWS SDK into your bundle.

Canonical: https://colis-site.vercel.app/en/react · Markdown: https://colis-site.vercel.app/en/react.md · Français: https://colis-site.vercel.app/react

The delivery page is one way to hand work over. If your clients already log into a portal of yours, the hooks put the same sending and pickup there: a file input, a code field that repairs typos, a download. The AWS SDK stays on your server.

Not on npm yet: the packages are built from [the repository](https://github.com/mamadouwhile/colis) until they are published.

**point it at the transfer routes**
```tsx
import { ColisProvider } from '@colis/react'

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

## A file input, a code.
sendFile() takes a File straight off an input, keeping its name and type. Failures land in error rather than rejecting, because an event handler should not need a try/catch.

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

function SendDeliverable() {
  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>Send your client this code: {transfer.code}</p>}
      {error && <p>{error.message}</p>}
    </>
  )
}
```

The hook posts to the transfer routes on your server, which hold the bucket credentials. The browser never sees a key, and your `authorize` function decides who may send.

`transfer` carries the code, the size and the expiry. Show the code grouped in fours; the receiving side accepts it with or without the spaces.

## Look it up, show it, then download.
load() fetches what a code holds without moving the bytes, so the client sees a filename and a size before anything is downloaded. loadBytes() brings the file across.

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

function ClientPickup() {
  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}>
        Find my delivery
      </button>
      {notFound && <p>Unknown or expired code.</p>}
      {transfer?.kind === 'file' && (
        <button onClick={download}>
          Download {transfer.filename} · {transfer.size} bytes
        </button>
      )}
    </>
  )
}
```

The receipt and the two answers are the delivery page’s own routes, not the hooks’: post to `/:code/opened` and `/:code/verdict` yourself, or send your clients to the page.

## What the client typed stays untouched.
useSyncCodeInput does the repair in the browser, before any request. Rewriting the field under the cursor is the one thing that makes these inputs miserable, so it never does.

`value` is verbatim. `code` is the canonical form to submit, `null` while what is typed cannot be one. `isComplete` is the moment to enable the button.

`inputProps` carries the keyboard and autofill hints a one-time code wants: `autoComplete="one-time-code"`, capitals, no autocorrect, and a numeric keyboard when the alphabet is digits.

Pass the same shape your server configured, `{ length: 4, alphabet }`, and both halves follow.

## A client hammering a button gets one answer.
Every call aborts the one before it, a late reply from a superseded call is dropped rather than published, and nothing is written after unmount.

- **Client hooks, App Router ready** — Every export is a client hook and the build carries `'use client'`, so it drops straight into the Next.js App Router. React 18 or later.
- **Tokens and custom clients** — Pass `headers` to the provider for a token, or `client` to bring your own, which is also how you drive it in tests with no network at all.
- **Status you can render** — `status` is idle, pending, success or error, and `notFound` covers both an unknown and an expired code, the way the protocol does.

- `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()`: the underlying client, for anything the hooks do not cover
