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

# State & Lifecycle

Know exactly what the avatar is doing at every moment — and build UI that responds to it.

## Two things to track

The SDK tracks two things separately, and keeping them apart is most of what there is to know:

- **The call** — is there a live session? `isIdle` → `isConnecting` → `isConnected`
- **The avatar** — is there something on screen? `isLoading` → `isReady`, or `isFailed`

They move independently. A previously-loaded avatar appears on screen before any call starts, and a call that drops leaves the avatar right where it was. So don't read one from the other: `isIdle` doesn't mean there's no avatar, and `isReady` doesn't mean you're in a call.

    call
    IDLE
    →
    CONNECTING
    →
    CONNECTED
    →
    IDLE

    avatar
    LOADING
    →
    READY
    |
    FAILED

    The two rows advance on their own clocks. Ending a call returns the top row to IDLE and leaves the bottom row untouched.

## The call

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `IDLE` | `isIdle = true` | — | No call. start() is available — including while the avatar is still loading, because start() waits the load out for you. |
| `CONNECTING` | `isConnecting = true` | — | start() is in flight: fetching a token, resolving the avatar, opening the connection, waiting for the server. True for the whole window from the tap to the call going live. |
| `CONNECTED` | `isConnected = true` | — | The call is live. The turn states below only apply from here. |

## The avatar

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `LOADING` | `isLoading = true` | — | The avatar is coming up — being resolved, or resolved and still rendering. |
| `READY` | `isReady = true` | — | The avatar is on screen and animating. |
| `FAILED` | `isFailed = true` | — | The avatar could not be loaded, so there is nothing on screen. start() tries again. |

## During a call

These only mean anything while `isConnected`. Ending a call clears them all.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `GREETING` | `isSpeaking = true` | — | Speaking the opening line, when avatar.greeting.enabled is set. It starts ~500ms after the session goes live, so the connect tone's fade can finish first. |
| `LISTENING` | `isListening = true` | — | Waiting for user voice input. Call mode only. |
| `THINKING` | `isThinking = true` | — | The avatar is working out its reply, between listening and speaking. |
| `SPEAKING` | `isSpeaking = true` | — | Avatar is talking with real-time lip-sync animation. |
| `INTERRUPTED` | `—` | — | Speech was cut short by interrupt() or stopSpeaking(). Returns to LISTENING. |

### The slow-turn flag

`isResponseSlow` sits alongside these rather than inside them. It means the
current turn is **dragging**, not that anything is wrong — the turn is still
coming. It auto-clears when speech starts or the turn ends.

The built-in thinking indicator renders off exactly this flag, which is why it
never appears on a fast turn. Turn it off with
`thinkingIndicator: { enabled: false }` and render your own if you want it
somewhere else on the page.

## Mute and turn-holding

Independent of everything above — muting does not change the session or turn
state, it changes what audio moves.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `isMicMuted` | `boolean` | — | Outbound audio is dropped, so the server genuinely hears silence. Not a UI-only flag. Call mode. |
| `isSpeakerMuted` | `boolean` | — | Playback is silent, but lip-sync keeps animating so the avatar does not look frozen. |
| `isPushToTalk` | `boolean` | — | The session is running in push-to-talk. Static — it never changes mid-session. |
| `isTalking` | `boolean` | — | The user is holding the talk button, so their audio is reaching the server. |

## State properties from useAvatar

```tsx
const {
  // The call
  isIdle,       // No call — start() is available
  isConnecting, // start() is in flight, from the tap to live
  isConnected,  // The call is live
  isSpeaking,     // Avatar is talking
  isListening,    // Waiting for user input (call mode)
  isThinking,     // Working out a reply
  isResponseSlow, // This turn is dragging — advisory, not an error

  // The avatar
  isReady,      // On screen and animating
  isLoading,    // Still coming up
  isFailed,     // Couldn't be loaded — nothing on screen

  // Audio
  isMicMuted,
  isSpeakerMuted,

  error,        // Most recent error, or null. Sticky until the next start()
  notice,       // Last server notice (plan/limit/test key). Not an error.
  health,       // Live connection health
} = useAvatar();
```

## Building a state-aware UI

The cleanest split is to let each axis drive the thing it's actually about. `` already handles its own loading and failure states, so your controls only need the call:

```tsx
function CallButton() {
  const { isIdle, isConnecting, start, stop } = useAvatar();

  if (isConnecting) return <button disabled>Connecting…</button>;
  return isIdle
    ? <button onClick={() => start()}>Start call</button>
    : <button onClick={() => stop()}>End call</button>;
}
```

That button is correct from the first render. It says "Start call" while the avatar is still loading — and it works, because `start()` waits for the load.

If you do want one status line covering both, answer the avatar first, since there's no point reporting on a call to an avatar that isn't there:

```tsx
function AvatarStatus() {
  const {
    isIdle, isConnecting, isConnected, isLoading, isFailed,
    isListening, isThinking, isSpeaking,
  } = useAvatar();

  if (isFailed)     return <span className="text-red-400">Avatar unavailable</span>;
  if (isLoading)    return <span className="text-yellow-400 animate-pulse">Loading avatar…</span>;
  if (isConnecting) return <span className="text-yellow-400 animate-pulse">Connecting…</span>;
  if (isListening)  return <span className="text-emerald-400 animate-pulse">Listening</span>;
  if (isThinking)   return <span className="text-cyan-400">Thinking</span>;
  if (isSpeaking)   return <span className="text-blue-400">Speaking</span>;
  if (isConnected)  return <span className="text-emerald-400">Connected</span>;
  if (isIdle)       return <span className="text-white/30">Ready to call</span>;

  return null;
}
```

## State vs events — when to use which

    useAvatar state
    Use for rendering UI that reflects the current state. React re-renders when state changes.

      {"// Conditional rendering\n"}
      {"const { isSpeaking } = useAvatar();\n"}
      {"return isSpeaking ?  : null;"}

    useAvatarEvent
    Use for side effects on state transitions — analytics, logging, auto-reconnect logic.

      {"// Side effects\n"}
      {"useAvatarEvent(\"speaking\", () => {\n"}
      {"  analytics.track(\"spoke\");\n"}
      {"});"}

> A good mental model: **state is for rendering, events are for reacting.** If you're rendering JSX based on avatar status, use `useAvatar`. If you're triggering an action when the status changes, use `useAvatarEvent`.
