> ## Documentation Index
> Fetch the complete documentation index at: https://docs.daily.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Build a working multi-participant video call in React with Daily React, from install to a rendered call grid.

This guide builds a minimal but complete custom call: it joins a room, renders a tile per participant, and plays everyone's audio. By the end you will have the full pattern that every Daily React app is built on.

<Note>
  You need a Daily room URL to follow along. Create a free account at [daily.co](https://www.daily.co) and create a room to get a URL like `https://your-domain.daily.co/room-name`.
</Note>

## The shape of a Daily React app

Three pieces appear in every app:

1. A **[`DailyProvider`](/reference/daily-react/daily-provider)** that creates the call object from your room `url` and shares it with the tree.
2. **Hooks** inside the provider that read call state.
3. **Components** like [`DailyVideo`](/reference/daily-react/daily-video) and [`DailyAudio`](/reference/daily-react/daily-audio) that render media.

<Steps>
  <Step title="Install the packages">
    Daily React needs `@daily-co/daily-js` and `jotai` as peer dependencies.

    <CodeGroup>
      ```bash npm theme={null}
      npm install @daily-co/daily-react @daily-co/daily-js jotai
      ```

      ```bash yarn theme={null}
      yarn add @daily-co/daily-react @daily-co/daily-js jotai
      ```

      ```bash pnpm theme={null}
      pnpm add @daily-co/daily-react @daily-co/daily-js jotai
      ```
    </CodeGroup>
  </Step>

  <Step title="Wrap your app in DailyProvider">
    [`DailyProvider`](/reference/daily-react/daily-provider) holds the call object and shares it with every component in your tree. The simplest setup is to let the provider create the call object for you: pass your room `url` straight to it.

    ```jsx theme={null}
    import { DailyProvider } from '@daily-co/daily-react';

    const ROOM_URL = 'https://your-domain.daily.co/room-name';

    function App() {
      return (
        <DailyProvider url={ROOM_URL}>
          <Call />
        </DailyProvider>
      );
    }
    ```
  </Step>

  <Step title="Join and leave the room">
    Get the call object with [`useDaily`](/reference/daily-react/use-daily) and call `join()` when the component mounts. The room `url` is already configured on the provider, so `join()` needs no arguments. Returning a cleanup function that calls `leave()` makes leaving automatic when the component unmounts.

    ```jsx theme={null}
    import { useEffect } from 'react';
    import { useDaily, useMeetingState } from '@daily-co/daily-react';

    function Call() {
      const daily = useDaily();
      const meetingState = useMeetingState();

      useEffect(() => {
        if (!daily) return;
        daily.join();
        return () => {
          daily.leave();
        };
      }, [daily]);

      if (meetingState !== 'joined-meeting') {
        return <p>Joining…</p>;
      }

      return <CallGrid />;
    }
    ```

    [`useMeetingState`](/reference/daily-react/use-meeting-state) re-renders the component as the meeting moves through `joining-meeting` to `joined-meeting`, so you can show a loading state without listening for events yourself.
  </Step>

  <Step title="Render a tile for each participant">
    [`useParticipantIds`](/reference/daily-react/use-participant-ids) returns the list of participant session IDs and updates as people join and leave. Map over it to render a tile per participant.

    ```jsx theme={null}
    import { useParticipantIds, DailyAudio } from '@daily-co/daily-react';

    function CallGrid() {
      const participantIds = useParticipantIds();

      return (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
          {participantIds.map((id) => (
            <Tile key={id} sessionId={id} />
          ))}
          {/* One DailyAudio for the whole call handles everyone's audio */}
          <DailyAudio />
        </div>
      );
    }
    ```

    <Tip>
      Render exactly one [`DailyAudio`](/reference/daily-react/daily-audio) for the whole call, not one per tile. It manages speaker slots and autoplay for every remote participant in a single place.
    </Tip>
  </Step>

  <Step title="Render each participant's video and name">
    [`DailyVideo`](/reference/daily-react/daily-video) renders a participant's video track given their `sessionId`. [`useParticipantProperty`](/reference/daily-react/use-participant-property) reads just the `user_name`, so the tile only re-renders when that one property changes.

    ```jsx theme={null}
    import { DailyVideo, useParticipantProperty } from '@daily-co/daily-react';

    function Tile({ sessionId }) {
      const userName = useParticipantProperty(sessionId, 'user_name');

      return (
        <div>
          <DailyVideo sessionId={sessionId} type="video" automirror />
          <span>{userName || 'Guest'}</span>
        </div>
      );
    }
    ```
  </Step>
</Steps>

## Complete example

A full working call. Set `ROOM_URL` to your room and render `<App />`.

```jsx App.jsx theme={null}
import { useEffect } from 'react';
import {
  DailyProvider,
  DailyAudio,
  DailyVideo,
  useDaily,
  useMeetingState,
  useParticipantIds,
  useParticipantProperty,
} from '@daily-co/daily-react';

const ROOM_URL = 'https://your-domain.daily.co/room-name';

export default function App() {
  return (
    <DailyProvider url={ROOM_URL}>
      <Call />
    </DailyProvider>
  );
}

function Call() {
  const daily = useDaily();
  const meetingState = useMeetingState();

  useEffect(() => {
    if (!daily) return;
    daily.join();
    return () => {
      daily.leave();
    };
  }, [daily]);

  if (meetingState === 'error') return <p>Something went wrong.</p>;
  if (meetingState !== 'joined-meeting') return <p>Joining…</p>;

  return <CallGrid />;
}

function CallGrid() {
  const participantIds = useParticipantIds();

  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
      {participantIds.map((id) => (
        <Tile key={id} sessionId={id} />
      ))}
      <DailyAudio />
    </div>
  );
}

function Tile({ sessionId }) {
  const userName = useParticipantProperty(sessionId, 'user_name');

  return (
    <div style={{ width: 320 }}>
      <DailyVideo
        sessionId={sessionId}
        type="video"
        automirror
        style={{ width: '100%', borderRadius: 8, background: '#000' }}
      />
      <span>{userName || 'Guest'}</span>
    </div>
  );
}
```

That is the entire pattern: create a call object, provide it, then read state with hooks and render media with components. No event listeners, no manual track attachment, no cleanup boilerplate.

## What just happened

* You never wrote a single `.on('participant-joined', ...)` listener. [`useParticipantIds`](/reference/daily-react/use-participant-ids) tracks that for you.
* Muting a mic or changing a name re-renders only the affected tile, because [`useParticipantProperty`](/reference/daily-react/use-participant-property) subscribes to one property at a time.
* Video and audio attach to the DOM through [`DailyVideo`](/reference/daily-react/daily-video) and [`DailyAudio`](/reference/daily-react/daily-audio), including the autoplay handling that is easy to get wrong by hand.

## Next steps

<CardGroup cols={2}>
  <Card title="Thinking in Daily React" icon="brain" href="/docs/daily-react/docs/thinking-in-daily-react">
    Why the hooks re-render the way they do.
  </Card>

  <Card title="Rendering media" icon="video" href="/docs/daily-react/docs/rendering-media">
    Video, audio, and screen tracks in depth.
  </Card>

  <Card title="Devices" icon="microphone" href="/docs/daily-react/docs/devices">
    Camera, mic, and speaker selection and toggling.
  </Card>

  <Card title="Working with participants" icon="users" href="/docs/daily-react/docs/participants">
    Filtering, sorting, and reading participant state.
  </Card>
</CardGroup>
