<!-- AvatarFactory Docs · https://avatarfactory.in/docs/sdk/react-native/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(). Returns to LISTENING. |

## 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

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

  error,        // Most recent error, or null. Sticky until the next start()
} = 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
import { Button } from "react-native";

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

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

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
import { Text } from "react-native";

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

  if (isFailed)     return <Text style={{ color: "#f87171" }}>Avatar unavailable</Text>;
  if (isLoading)    return <Text style={{ color: "#facc15" }}>Loading avatar…</Text>;
  if (isConnecting) return <Text style={{ color: "#facc15" }}>Connecting…</Text>;
  if (isListening)  return <Text style={{ color: "#34d399" }}>Listening</Text>;
  if (isThinking)   return <Text style={{ color: "#22d3ee" }}>Thinking</Text>;
  if (isSpeaking)   return <Text style={{ color: "#60a5fa" }}>Speaking</Text>;
  if (isConnected)  return <Text style={{ color: "#34d399" }}>Connected</Text>;
  if (isIdle)       return <Text style={{ color: "rgba(255,255,255,0.3)" }}>Ready to call</Text>;

  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 UI based on avatar status, use `useAvatar`. If you're triggering an action when the status changes, use `useAvatarEvent`.
