Calling State and Lifecycle

The call instance manages all call-related functionality: joining, actions (mute, reactions), and event subscriptions. Create instances via client.call(type, id). The StreamVideoClient maintains the WebSocket connection and handles API calls.

Best Practices

  • Create call instances only in effects with call.leave() cleanup to prevent memory leaks.
  • Handle all CallingState values exhaustively to show appropriate UI for each state.
  • Use useCallCallingState() hook for reactive state updates in React components.
  • Reuse call instances or create new ones with client.call(type, id) to rejoin.

Call instance lifecycle:

import { Call, StreamVideoClient } from "@stream-io/video-react-sdk";

let client: StreamVideoClient; // ...

const call: Call = client.call(type, id);

// load existing call information from our servers
await call.get();

// Creates the call on our servers in case it doesn't exist. Otherwise,
// loads the call information from our servers.
await call.getOrCreate();

// join the call
await call.join();

// leave the call and dispose all allocated resources
await call.leave();

Access calling state via:

  • call.state.callingState - current value
  • call.state.callingState$ - observable for changes
  • useCallCallingState() - React hook

Call Instance

Acquire with client.call(), dispose with call.leave(). Always create in effects with cleanup:

const [call, setCall] = useState<Call | undefined>(undefined);

useEffect(() => {
  const myCall = client.call(callType, callId);
  myCall.join({ create: true }).then(
    () => setCall(myCall),
    () => console.error("Failed to join the call"),
  );

  return () => {
    myCall.leave().catch(() => console.error("Failed to leave the call"));
    setCall(undefined);
  };
}, [callType, callId]);

To rejoin, reuse the instance or create a new one with client.call(type, id).

Calling State

The CallingState enum exposes call state:

import { CallingState, useCallStateHooks } from "@stream-io/video-react-sdk";

const { useCallCallingState } = useCallStateHooks();
const callingState = useCallCallingState();

switch (callingState) {
  case CallingState.JOINED:
    // ...
    break;
  default:
    const exhaustiveCheck: never = callingState;
    throw new Error(`Unknown calling state: ${exhaustiveCheck}`);
}

Handle CallingState exhaustively - TypeScript will warn if new states are added.

Calling States

StateDescription
CallingState.UNKNOWNThe state is unknown. This value is set when Calling State isn't initialized properly.
CallingState.IDLEA call instance is created on the client side but a WebRTC session isn't established yet.
CallingState.RINGINGThis is an incoming (ring) call. You are the callee.
CallingState.JOININGThe call join flow is executing (typically right after call.join()). Our systems are preparing to accept the new call participant.
CallingState.JOINEDThe join flow has finished successfully and the current participant is part of the call. The participant can receive and publish audio and video.
CallingState.LEFTThe call has been left (call.leave()) and all allocated resources are released. Please create a new call instance if you want to re-join.
CallingState.RECONNECTINGA network connection has been lost (due to various factors) and the call instance attempts to re-establish a connection and resume the call.
CallingState.RECONNECTING_FAILEDThe SDK failed to recover the connection after a couple of consecutive attempts. You need to inform the user that he needs to go online and manually attempt to rejoin the call.
CallingState.MIGRATINGThe SFU node that is hosting the current participant is shutting down or tries to rebalance the load. This call instance is being migrated to another SFU node.
CallingState.OFFLINENo network connection can be detected. Once the connection restores, the SDK will automatically attempt to recover the connection (signalled with RECONNECTING state).

Calling State transitions: regular path

State transitions when joining a call as caller or callee:

stateDiagram-v2
  direction LR
  [*] --> IDLE
  IDLE --> RINGING: call.join({ ring })
  RINGING --> JOINING
  IDLE --> JOINING: call.join()
  JOINING --> JOINED
  JOINED --> LEFT: call.leave()
  LEFT --> [*]

Calling State transitions: reconnects and migration

State transitions after network interruptions or during SFU node shutdown/rebalancing:

stateDiagram-v2
  direction LR
  [*] --> JOINING
  JOINING --> JOINED: successful join

  JOINED --> RECONNECTING: network glitch/switch
  RECONNECTING --> JOINING: attempt to reconnect
  RECONNECTING --> RECONNECTING_FAILED

  JOINED --> LEFT: call.leave()
  LEFT --> [*]

  JOINED --> MIGRATING: initiate server migration
  MIGRATING --> JOINING: attempt to migrate

  JOINED --> OFFLINE: went offline
  OFFLINE --> RECONNECTING

Example handling

import {
  CallingState,
  useCall,
  useCallStateHooks,
} from "@stream-io/video-react-sdk";

const call = useCall();
const isCallCreatedByMe = call?.isCreatedByMe;

const { useCallCallingState } = useCallStateHooks();
const callingState = useCallCallingState();

switch (callingState) {
  case CallingState.UNKNOWN:
  case CallingState.IDLE:
    return <LobbyScreen />;

  case CallingState.RINGING:
    return isCallCreatedByMe ? <OutgoingCallScreen /> : <IncomingCallScreen />;

  case CallingState.JOINING:
    return <LoadingCallScreen />;

  case CallingState.JOINED:
    return <ActiveCallScreen />;

  case CallingState.LEFT:
    return <HaveANiceDayScreen />;

  case CallingState.RECONNECTING:
  case CallingState.MIGRATING:
    return <RestoringConnectionScreen />;

  case CallingState.RECONNECTING_FAILED:
    return <GeneralConnectionErrorScreen />;

  case CallingState.OFFLINE:
    return <NoConnectionScreen />;

  default:
    const exhaustiveCheck: never = callingState;
    throw new Error(`Unknown calling state: ${exhaustiveCheck}`);
}