Build a video chat app in minutes with React and daily-js
Tags: react, video, daily, javascript
Published: 2020-09-14 22:13:52

With video chat apps on the rise for, well, [obvious reasons](https://www.statista.com/chart/21268/global-downloads-of-video-chat-apps-amid-covid-19-pandemic/), it’s increasingly important to be able to add video call capabilities to apps and websites quickly. The more customizable those video calls can be, the better for building unique user experiences.

This post walks you through how to build a custom video chat app with React and [the Daily API](https://docs.daily.co/docs/reference-docs).

![Two people smiling in a video call in the React demo app](https://dev-to-uploads.s3.amazonaws.com/i/kz93gozlchbr1clwsvhl.png)

## What we’ll build   
In our app, when a user clicks to start a call, the app will create a [meeting room](https://docs.daily.co/reference#rooms), pass the room’s URL to a new [Daily call object](https://docs.daily.co/docs/build-a-custom-video-chat-interface#daily-call-object), and join the call. The call object is something that keeps track of important information about the meeting, like other participants (including their audio and video tracks) and the things they do on the call (e.g. muting their mic or leaving), and provides methods for interacting with the meeting. The app leverages this object to update its state accordingly, and to carry out user actions like muting or screen-sharing. When the user leaves the meeting room, the call object is destroyed.

## What you’ll need to build it 
* **Daily account**: [Sign up for an account](https://dashboard.daily.co/signup) if you don’t have one already.   
* **Cloned `daily-demos/call-object-react` Github repository**: The fastest way to follow along with this tutorial and get a demo app up and running is by [cloning this repo](https://github.com/daily-demos/call-object-react). 
* **Familiarity with React**: In this post we skip over a lot of the code that isn’t related to Daily, so it could be worth brushing up on React and/or hooks [0]. 

Once you have those things, we’re ready to get started! 

## Build and run the app  
After you’ve [cloned the repository](https://github.com/daily-demos/call-object-react):

```
cd call-object-react
npm i
npm run dev
```

Now, open your browser and head to `localhost:<port>`, using the port printed in the terminal after running the above. You should have an app running locally that mirrors the [live demo](https://call-object-react.netlify.app/): you can click to start a call, and share the link with another participant or yourself in a new tab. 

![Anakin Skywalker in Phantom Menace exclaims it’s working](https://media.giphy.com/media/BoBOKNtlR8rTi/giphy.gif)  

It’s great that it’s working...but _how_ is it working? 

## How the app works 

### Daily concepts 
Before diving into the code, let’s cover some Daily fundamentals. 

#### The call object 
A Daily call object is like a direct line to the Daily API. It gives us the finest-grained control over a video call, letting us access its lowest level foundations, like participant video and audio tracks. Invoking `DailyIframe.createCallObject()` creates a call object. Once created, we pass a meeting room URL to the call object to join a call. 

In addition to all of that, the call object keeps track of our call’s state, both meeting state and participant state. 

##### State #1: meeting state 
Meeting state tracks where a current (often called "local") participant is in the life of a call. A participant can start to join a call, be in a call, have left a call, or be experiencing an error. 

We can check a call’s meeting state via the call object with `callObject.meetingState()`. If a participant is joining a meeting, the "joining-meeting" event will be returned, for example. 

Meeting state changes trigger events like "joining-meeting." We can add event listeners for those state changes with `callObject.on()`. 

##### State #2: participant state 
Participant state monitors  _all_ participants on the call (including the current user) and the video, audio, or other media they’re sharing with everybody else. 

`callObject.participants()` returns a set of participant objects, keyed by an ID (or "local", for the current user). Each participant object includes fields like `tracks`, including the participant's raw audio and video tracks and their playable states.

The events "participant-joined", "participant-left", and "participant-updated" broadcast changes to participant state. The first two are only sent when participants _other_ than the current local participant join or leave, while the latter fires on changes like camera and microphone toggling for any participant, including local. 

Now that we’ve covered the Daily call object and its states, we’re ready to look at our app. 

###What’s happening in the code 

###App.js: Creating, joining, and leaving a call 
Before we get into the details of each stage of a meeting, let’s look at how we wire up our top-level event listeners. In App.js, we listen for changes to `callObject.meetingState()`, so we can update the UI for the local participant depending on where they’re at in their user journey: in a call, out of a call, or experiencing errors: 

{% gist https://gist.github.com/kompfner/3687ba6d3d3ff3d70c6192284aae0851 %} 

When a local participant leaves a meeting, we call `callObject.destroy()`. We do this to clean up our call object's global footprint, to open the door for our app to create another call object in the future with different create-time options.    

#### Creating a call 
When a participant clicks to start a call, they invoke the `createCall()` function to create a short-lived, demo-only room. 

{% gist https://gist.github.com/kimberleejohnson/eea0541f8513c2fa1e513f51723777cd %} 

In real production code you'll want to create rooms by calling the [Daily REST API](https://docs.daily.co/reference#your-dailyco-domain) from your backend server, to avoid storing API keys in your client-side JavaScript [1].

#### Joining a call 
Once we have a room, we’ll join it by invoking the [`.join()` method](https://docs.daily.co/reference#%EF%B8%8F-join) on the call object [2]. 

{% gist https://gist.github.com/kimberleejohnson/81b0dfac4e87d0b09f340a6ee7873ca8 %} 

#### Leaving a call
When a participant clicks the “Leave” button, we’ll initiate that process by invoking the [`leave()` method](https://docs.daily.co/reference#%EF%B8%8F-leave) on the call object [3, 4]. 

{% gist https://gist.github.com/kimberleejohnson/490f0fb4aa2d2fa1b7f068410ff482a1 %} 

###Call.js and callState.js: Using state to determine the call display 
We now know how different operations in a call take place, so the next step is to know how those operations affect our display. This involves keeping tabs on participant state in order to display the call's participants and their video and audio tracks.

While App.js listened to `callObject.meetingState()`, in Call.js we’ll listen for `callObject.participantState()` and update our component state accordingly [5].

{% gist https://gist.github.com/kompfner/db3e93bcf0911f84a83fe94cc38c2e6f %} 

Our demo app displays each participant (including the current user) as their own "tile", and also displays any screen share as its own tile independent of the participant doing the sharing.

To accomplish this, we map `callObject.participantState()` to the call’s component state, specifically into a set of "call items" in callState.js: 

{% gist https://gist.github.com/kimberleejohnson/c2964413b73e096c48d3d402ac31939f %} 

Each call item corresponds to a call participant, storing the participant’s video track, audio track, and a boolean that notes whether or not a participant is in the process of joining a call [6]. 

To populate the call items, we call our `getCallItems()` function, which loops over participant state: 

{% gist https://gist.github.com/kompfner/4410b1a1113dfea0a599e7b6638ae971 %} 

We import callState in Call.js, where we invoke the `getTiles()` function to pass participant video and audio tracks to their respective tile components. 

{% gist https://gist.github.com/kompfner/b2e0b4474af659a6360cce00794e1ffc %} 

Now let’s take a closer look at those tiles. 

![Pink and yellow tiles shift colors](https://media.giphy.com/media/l1J9K8V2MgXVX4vug/giphy.gif)

###Tile.js: displaying each participant’s video stream 
Each of our tile components contains either a `<video>` and/or an `<audio>` element. Each tag references its respective DOM element [7]. Note the `autoPlay muted playsInline` attribute. These are the set of attributes that will let your audio and video play automatically on Chrome, Safari, and Firefox.
{% gist https://gist.github.com/kompfner/651c9be62c6375dc4a1c8a3667ed45cb %} 

Next up: give participants control over whether or not they display their videos and share their audio or screens. 

###Tray.js: Enable participant controls 
Once again we'll use participant state to determine whether we're actively sharing audio, video, and our screen.

We’ll look specifically at `callObject.participants().local`, since we’re concerned about adjusting the user interface for the current, or local, user. The only event we need to listen to is "participant-updated" [8]. 

{% gist https://gist.github.com/kompfner/9b951785c81babec0a1ef54d5524ee8b %} 

With our event listener handling state updates, we can wire up our buttons to handle the relevant `callObject` methods to control user input: `.setLocalVideo`, `.setLocalAudio`, and `.startScreenShare` or `.stopScreenShare`. 

{% gist https://gist.github.com/kimberleejohnson/930de72ccc7e3a3f6afb82ed7db9e870 %} 

##What to add next 
Congratulations! If you’ve read this far, you now have an overview of your custom video chat app. To dig even deeper into the code, have a look at how the demo handles edge cases over [on the Daily blog](https://www.daily.co/blog/building-a-custom-video-chat-app-with-react/). Or, dive into our [demo repository](https://github.com/daily-demos/call-object-react). 

[![Button](https://dev-to-uploads.s3.amazonaws.com/i/ai2gn6g0011b7eqwu5zg.png)](https://github.com/kimberleejohnson/daily-demos/tree/master/react-demo)

To see everything else the Daily APIs have to offer, grab a cup of tea and head over to [docs.daily.co](https://docs.daily.co/docs) for some fun evening reading.

Thanks for reading! As always, we'd love to know what you think and how we can better help you to build that next great video chat app, so please don't hesitate to [reach out](https://www.daily.co/contact-us).

![Dinaosaur in a cup that has a tea rex tea bag](https://media.giphy.com/media/icVkqVBTfuBDczxyBH/giphy.gif)

### Footnotes 
[0] If you’d like to get familiar with React and come back later, check out the many great resources on DEV (like [Ali Spittel’s intro](https://dev.to/aspittel/a-complete-beginners-guide-to-react-2cl6)), or the React docs for more on [hooks](https://reactjs.org/docs/hooks-intro.html). 
[1] Our teammate wrote an excellent post on [how to set up an instant Daily server on  Glitch](https://www.daily.co/blog/deploy-a-daily-co-backend-node-js-server-instantly/).
[2] Note that, because we invoke `destroy()` on our call object after each call ends, we need to create a new call object in order to join a room. This is not strictly necessary — you could hold onto a single call object for the lifetime of your app if you so desired, but, as we mentioned earlier, we prefer this approach to leave the door open for a future differently-configured call object.
[3] You might’ve noticed that both the `join()` and `leave()` call object operations are asynchronous, as is `destroy()`. To avoid undefined behavior and app errors, like leaving and destroying a call object simultaneously, it's important to prevent triggering one call object operation while another is pending. A straightforward way to do this is to use meeting state to update relevant buttons' idle states so that the user can't start an operation until it's safe, like we do in [our demo app](https://github.com/daily-demos/call-object-react/blob/8564d4a6dd1b858e9f5eeab98231f3ebed81ecb6/src/components/App/App.js#L188).
[4] Because `destroy()` is asynchronous, the DailyIframe.createCallObject() must only be invoked once destroy()'s Promise has been resolved.
[5] In the demo app, we use a reducer to update the component state.
[6] We only set `isLoading` to true if we’ve _never_ received audio or video tracks for a participant. 
[7] We did that so we could programmatically set their `srcObject` properties whenever our media tracks change (see [lines 18-31 in Tile.js](https://github.com/daily-demos/call-object-react/blob/8564d4a6dd1b858e9f5eeab98231f3ebed81ecb6/src/components/Tile/Tile.js#L18)).
[8] You might remember that "participant-joined" and "participant-left" are only ever about other (not local) participants.