# useEventSource

Live updates from the server over Server-Sent Events (`EventSource`): connects while `url` is set, closes on unmount or when `url` changes or is `null`, and reconnects on its own.

Browsers retry dropped connections themselves, but give up for good when the server answers with an error status; this hook keeps retrying then, waiting 1s, 2s, 4s, up to 30s between attempts. With `pauseWhenHidden`, a background tab holds no connection.

`data` is only the latest message: when several arrive at once, a render may see just the last. To keep every message (a feed, a log), use `onMessage`. Messages are strings; `JSON.parse` them yourself.

## Import

```ts
import { useEventSource } from '@vincent.ri/react-tils'
// or only this hook
import { useEventSource } from '@vincent.ri/react-tils/useEventSource'
```

## Usage

```ts
const { status, data } = useEventSource(`/api/jobs/${id}/progress`)
const percent = data ? JSON.parse(data).percent : 0
return <progress value={percent} max={100} aria-busy={status !== 'open'} />

// Keep every message
const [items, setItems] = useState<Notification[]>([])
useEventSource('/api/notifications', {
  onMessage: (event) => setItems((list) => [JSON.parse(event.data), ...list]),
  pauseWhenHidden: true,
})
```

## When to use it

One-way live updates: notifications, progress of a job, live scores or prices, a dashboard, logs.

## When not to

Streaming an AI response from a `POST`; `EventSource` can only `GET` and can't set headers, so read `fetch()`'s body stream (or use your AI SDK). For two-way messaging, use a WebSocket library. To check for changes every minute or so, polling with `useInterval` is simpler.

## API

```ts
function useEventSource(url: string | null, options?: EventSourceOptions): EventSourceState
```

| Parameter | Description |
| --- | --- |
| `url` | The stream's URL, or `null` to stay disconnected. |
| `options` | `onMessage`, `events`, `withCredentials`, `pauseWhenHidden`. Optional. |

**Returns** `{ status, data }`.

### Types

```ts
export type EventSourceStatus = "connecting" | "open" | "closed";
export type EventSourceOptions = {
  /** Called for every message, including named `events`. Can change every render. */
  onMessage?: (event: MessageEvent<string>) => void;
  /** Named events to receive too (`event: price` in the stream), besides unnamed messages. */
  events?: string[];
  /** Send cookies to another origin. Default `false`. */
  withCredentials?: boolean;
  /** Disconnect while the tab is hidden, and reconnect when it is visible again. Default `false`. */
  pauseWhenHidden?: boolean;
};
export type EventSourceState = {
  /** `'connecting'` (also while retrying), `'open'`, or `'closed'` (no `url`, or paused). */
  status: EventSourceStatus;
  /** The latest message's `data`, or `null` before the first. Resets when `url` changes. */
  data: string | null;
};
```

## Browser support

Works in all modern browsers.

## Coming from another library?

Other libraries call this `useEventSource` (VueUse), `useSSE`, `useServerSentEvents`.

---

547 B minified and brotlied
Source: https://github.com/vincentri/react-tils/blob/main/src/useEventSource.ts
Docs page: https://vincentri.github.io/react-tils/hooks/useEventSource/
Previous: [useSpeechRecognition](https://vincentri.github.io/react-tils/hooks/useSpeechRecognition.md)
Next: [useScript](https://vincentri.github.io/react-tils/hooks/useScript.md)
