Skip to main content
The call object is the heart of every Daily app: it is the daily-js instance that joins rooms, sends media, and emits events. DailyProvider holds it and shares it with your component tree. In most apps, the provider creates and manages the call object for you.

Let DailyProvider create the call object

The simplest and recommended setup is to pass your room url (and any other factory options such as token or userName) directly to DailyProvider. The provider creates the call object, manages its lifecycle, and tears it down for you.
This is the right default for most apps: there is no instance to create, store, or clean up, and it is the hardest setup to get wrong.
The provider recreates the call object whenever a creation prop’s value changes (url, token, and so on), tearing down the current call and resetting all call and error state. These props are compared by value, so re-passing an equivalent object on each render is safe; only an actual value change triggers a rebuild, and nothing defers it while a call is in progress. Treat creation props as stable configuration for the duration of a call.

Create the call object yourself

Create the instance yourself only when you need direct control over it, for example to access it before the provider mounts, share it outside the provider tree, or create it conditionally. Use useCallObject, then pass it to the provider with the callObject prop:
Use useCallObject rather than calling Daily.createCallObject() yourself. It deduplicates instances, which prevents the Duplicate DailyIframe instances are not allowed error that surfaces under React Strict Mode (where effects intentionally run twice in development).
To delay creation until some condition is met, pass shouldCreateInstance:

Access it anywhere with useDaily

Inside the provider, useDaily returns the same DailyCall instance. Use it for imperative actions that do not have a dedicated hook:
daily is null until the call object exists, so guard with optional chaining or an early return.

The call lifecycle

A custom call moves through the same lifecycle as any daily-js call. useMeetingState gives you the current stage reactively:

Joining and leaving

There is no useJoin hook: joining is an imperative action, so call join() on the object from useDaily. A mount effect with a cleanup that calls leave() is the idiomatic pattern:
If you set url (and token) on DailyProvider as recommended above, the call object already has them, so you can call daily.join() with no arguments. Pass them to join() explicitly only when they are not already on the provider. The instance is reusable: after leave(), the meeting state becomes left-meeting and you can join() again.

Handling errors

useDailyError exposes the most recent fatal and nonfatal errors as state:
A fatal error ejects the participant from the meeting immediately. If you render UI from meetingError, do not destroy the call object (or change DailyProvider’s creation props, which destroys it internally) until you no longer need the error state.

Embedding Prebuilt instead of a custom UI

If you want Daily’s ready-made UI, create a Prebuilt iframe with useCallFrame and pass it to the same provider:
An iframe runs in a separate secure context, so track-level data is not available across the boundary. Hooks that read meeting and participant state still work, but media hooks like useMediaTrack will not return tracks. For full control over media, use a call object instead.

Next steps

Working with participants

Read and react to participant state.

Rendering media

Show video and play audio.