<!-- AvatarFactory Docs · https://avatarfactory.in/docs/sdk/react-native/callbacks-events · Full map: https://avatarfactory.in/llms.txt -->

# Events

Run your own code when the session connects, the avatar speaks, or an error happens.

## Event map

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `start` | `() => void` | — | Session connected and is active. Safe to call speakText, play, etc. |
| `stop` | `() => void` | — | Session ended. Either from stop() or a disconnection. |
| `speaking` | `() => void` | — | Avatar began speaking and animating. |
| `interrupt` | `() => void` | — | Avatar speech was cut short via interrupt(). |
| `ready` | `() => void` | — | Avatar has rendered on screen for the first time. |
| `error` | `(error: AvatarError) => void` | — | An error occurred. Check error.fatal to know whether the session survived. |
| `notice` | `(notice: SessionNotice) => void` | — | A graceful, server-initiated message — a plan limit, a demo cap, a test key expiring. The avatar speaks it and the session then closes. Not an error. |
| `transcript` | `(entries: TranscriptEntry[]) => void` | — | The whole transcript, re-emitted as a snapshot on every change. Only fires when transcript.enabled is set. |
| `health` | `(health: ConnectionHealth) => void` | — | Connection health, re-emitted whenever the server reports stream health. |

> **`notice` is not `error`.** A notice means the session is ending
>   deliberately, and `notice.message` is written for your users — it is the same
>   text the avatar just said out loud. Drive upgrade CTAs off `notice`; never off
>   `error`.

## Subscribing with useAvatarEvent (recommended)

`useAvatarEvent` handles cleanup automatically when the component unmounts.

```tsx
import { useAvatarEvent } from "@avatarfactory/react-native";

function MyScreen() {
  useAvatarEvent("start", () => console.log("Connected!"));
  useAvatarEvent("stop",  () => console.log("Disconnected."));

  useAvatarEvent("error", (error) => {
    if (error.fatal) {
      console.error("Session ended:", error.code);
    } else {
      console.warn("Session continues:", error.message);
    }
  });

  return null;
}
```

## Subscribing with on/off (manual)

Use when you need to subscribe outside the component lifecycle, or group multiple events in one `useEffect`.

```tsx
import { useEffect } from "react";
import { useAvatar } from "@avatarfactory/react-native";

function MyScreen() {
  const { on, off } = useAvatar();

  useEffect(() => {
    const onStart = () => console.log("Started");
    const onError = (err) => console.error(err.code, err.message);

    on("start", onStart);
    on("error", onError);

    return () => {
      off("start", onStart);
      off("error", onError);
    };
  }, [on, off]);

  return null;
}
```

> Always return a cleanup function from `useEffect` that calls `off` for every `on`. Missing cleanup causes memory leaks and stale handlers firing after the screen unmounts.

> The `error` state on `useAvatar` is sticky: it stays set until the next `start()`, so you can render off it safely. The `error` event fires once, in real time — use `useAvatarEvent("error", ...)` when you need every occurrence (analytics, logging).

## Examples

### Live session badge

```tsx
import { Text } from "react-native";

function LiveBadge() {
  const [isLive, setIsLive] = useState(false);

  useAvatarEvent("start", () => setIsLive(true));
  useAvatarEvent("stop",  () => setIsLive(false));

  return (
    <Text style={{ color: isLive ? "#22c55e" : "rgba(255,255,255,0.3)" }}>
      {isLive ? "● Live" : "○ Offline"}
    </Text>
  );
}
```

### Session timer

```tsx
import { Text } from "react-native";

function SessionTimer() {
  const [seconds, setSeconds] = useState(0);
  const timerRef = useRef<ReturnType<typeof setInterval>>();

  useAvatarEvent("start", () => {
    timerRef.current = setInterval(() => setSeconds((s) => s + 1), 1000);
  });

  useAvatarEvent("stop", () => {
    clearInterval(timerRef.current);
    setSeconds(0);
  });

  return <Text style={{ color: "rgba(255,255,255,0.5)" }}>Session: {seconds}s</Text>;
}
```
