Build a Video Conferencing App Using Dolby Voxeet and Stream Chat
Tags: video, chat, stream, voxeet
Published: 2019-10-30 00:02:29

![Voxeet](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/b715bffd0ccb0e04c58bd183a114cd66/image4.png?auto=compress%2Cformat&ixlib=php-1.2.1)

With the recent launch of [Stream Chat](https://getstream.io/chat/), the team here has been working with vendors to add real-time chat and messaging to various platforms. We’ve had several requests for voice and video chat, and after looking around at the market, it was a clear choice to partner up with [Voxeet](https://voxeet.com) (recently acquired by [Dolby](https://dolby.com)). The proof is in the pudding, so we built out a fully functional proof of concept for a live video-conferencing application using Stream Chat and Voxeet as a web application with React.

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/503da6666b57a604c5e6e88770c17ce7/image3.png?auto=compress%2Cformat&ixlib=php-1.2.1)

The reason for our decision to use Voxeet over other competitors such as [Twilio’s Programmable Video platform](https://www.twilio.com/video) was primarily due to the following:

- [Voxeet](https://voxeet.com) is an industry leader in the video space and provides SDKs for popular languages – in either your choice of JavaScript, iOS (Swift), or Android (Java)
- Predictable and generous [pricing](https://www.voxeet.com/pricing/) model that flat out makes sense – it’s based on the number of voice and video minutes
- Their [documentation](https://developer.voxeet.com/getstarted/javascript/getstarted-javascript/) is written for developers with an emphasis on how to integrate – including nice example tutorials in the beginning
- They provide a React integration that works flawlessly with the [Stream Chat React components](https://getstream.github.io/stream-chat-react/) making it easy for developers to access the underlying Voxeet API and connect it with Stream Chat

Furthermore, our initial tests showed that Voxeet had substantially less latency and offered far more clarity when it came to real-time video (and their video conferencing API) compared to Twilio. With better documentation, we knew ahead of time that we would be able to execute without running into any hiccups.

For the rest of this post, we will outline how we went about building a competitor similar to [Zoom](https://zoom.us/) (the video communication tool) in less than a week. We'll be using Voxeet for live video (via [WebRTC](https://webrtc.org/)) as well as [Stream Chat](https://getstream.io/chat/) for real-time chat/messaging capabilities.

> **Note**: _You can find the demo located [here](https://stream-dolby.netlify.com/). The full code for the frontend UI is available on [GitHub](https://github.com/GetStream/stream-dolby-voxeet). If you would like to run the demo using a backend API, please see this [GitHub repo](https://github.com/GetStream/stream-dolby-voxeet-api)._ 

## Prerequisites

To fully follow along with this tutorial, you’ll want to make sure that you have a decent understanding of the following:

- JavaScript / Node.js
- React
- Redux / Redux Saga

You will also want to ensure that you have the following installed on your machine:

- [Homebrew](https://brew.sh/) (Latest)
- [Node.js](https://nodejs.org/en/) (v12.10.0 or above)
- [Yarn](https://yarnpkg.com/lang/en/) (Latest)
- [Create React App](https://github.com/facebook/create-react-app) (CRA)

And, you’ll need to have created free accounts with the following services:

- [Heroku](https://signup.heroku.com/login) (for hosting the API – free tier)
- [Stream](https://getstream.io/chat/) (for real-time chat functionality – free 14-day trial)
- [Voxeet](https://developer.voxeet.com/dashboard/register) (for video conferencing – free tier)

> **Note**:  _We’ll be using the latest version of Chrome on macOS to test throughout this tutorial._

## Getting Started

## The Build

The build-out of this application is rather straightforward. It requires two primary elements – the frontend UI and the backend API. If you want to take it a step further, you can host the frontend CRA application on Netlify and the API on Heroku in just a few minutes.

### Step 1: Frontend UI

First, make sure you have an account set up with both [Stream](https://getstream.io/chat/) and [Voxeet](https://developer.voxeet.com/dashboard/register), and you have all the keys and tokens for each in hand, you will need the following:

- Stream Key
- Stream Secret
- Voxeet Key
- Voxeet Secret

Next, run `npx create-react-app stream-voxeet` in your terminal. Once complete, add the Stream API key and the Voxeet keys from the associated dashboards to a `.env` file within your project's root directory for safe-keeping.

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/0f5f8c8ebe04ba747597e2cf396f7c48/image1.png?auto=compress%2Cformat&ixlib=php-1.2.1)

> **Note**: _With create-react-app, you must prepend all environment variables with REACT_APP._

You will need the following environment variables in your `.env` file:

- REACT_APP_STREAM_KEY
- REACT_APP_VOX_KEY
- REACT_APP_VOX_SECRET

Head back to your terminal and install the dependencies we need for the project:
 
https://gist.github.com/nparsons08/e4ec99bab16cd5bca596500db9c457f4

> **Note**: _In our initial testing we realized that the Voxeet components currently throw an error with the latest version of react-redux. Make sure you enter react-redux@5.1.1 when you install the dependencies above._

Finally, create a `jsconfig.json` file in the root of your application. Copy and paste the following:

https://gist.github.com/nparsons08/a26454567780ec0d4b405ffd694f862a

> **Note**: _The jsconfig.json file will allow aliased imports based on your directory structure without having to use absolute paths to your modules and components. E.g. import Button from components/Button._

Now that our project is all set up let’s jump into our code editor and start building.

### Step 2: Setup

First up, we’ll get our Redux store, middleware, and styled-components theme set up, as well as removing the defaults provided by the CRA boilerplate. 

Next, navigate to the `src` directory in your project and remove the `index.css`, `app.css`, and `logo.svg` files.

You’ll want to _remove_ the imports for these assets in the `App.js` and the `index.js` files, as well as strip all boilerplate markup out of `App.js` – leaving only the parent `div`.

> **Note**: _You can also remove the className prop as we will replace all of the CSS with styled-components. Doing so will provide a theme file where you can easily tweak aspects of the design of the app once it is fully built._

Then, create the following directories inside your `/src` directory:

- components
- containers
- data
- styles
- screens

#### Styles

We’ll start by creating a couple of files inside of the `styles` directory. First, create `styles/colors.js` and `styles/breakpoints.js` respectively, and paste the following:

**Colors**:

https://gist.github.com/nparsons08/51da31206d95631308735dcc712f3f55

**Breakpoints**:

https://gist.github.com/nparsons08/d79b0f1940391023739291db0cad542e

Next, we will import these variables in a moment to help populate our styled-components theme. Then, we’ll create a `colorUtils.js` file and paste the following code inside:

https://gist.github.com/nparsons08/e2b65df7ee344a7b0034cfce95c9b7d0

> **Note**: _This file is adapted from v0.x of Material UI and offers handy utilities for handling colors – such as converting hex to RGB/RGBA and quickly altering the opacity, hue, and saturation of the color just by passing in a hex value. We often add this to our theme definition for easy access inside styled-components._

Now, we can bring this all together in `styles/theme.js` – this theme will be available inside all of our styled-components and accessible through the `withTheme` HOC.

https://gist.github.com/nparsons08/8beeadf066444ab3c76061ca789f9974

Last, we’ll create `styles/global.js`. This file is effectively a drop-in replacement for `index.css` that we removed earlier. Please note that we’ll also import two additional files at a later time to override styles for both stream-chat-react and voxeet.

https://gist.github.com/nparsons08/a84d7085a899c1d8268201a869abf8ef

We’ll get this all tied together in our `App.js` shortly, but first, we’ll set up our Redux store.

#### Redux

Inside of your data directory, add a file called `createReducer.js` with the following code inside of it:

https://gist.github.com/nparsons08/e3cc8d3cad163762b6189b7aea85c3fe

The following code will hook the Voxeet reducer up to our Redux store and enable the `Conference` components to work correctly. We will also create a `rootSaga.js` file within the data directory; however, for now, we will leave it blank and come back to it later.

https://gist.github.com/nparsons08/5d40f4cd65253b6264996db14a40bd57

Next, let's create the Redux store itself in `data/createStore.js` to tie it all together – we will add the thunk middleware which is required under the hood in the Voxeet components, as well as the redux-saga middleware which we will use a bit later on when we add simple authentication to the app as well as some other fun stuff.

https://gist.github.com/nparsons08/2690da6cb7b6e2dbe0eb36702a7e5908

Now that our theme and our Redux store are ready to go, we’ll wrap our app in `ThemeProvider`, react-redux’s `Provider` and include our global styles and initial route definitions. To do this, open `App.js` and make sure it looks like the following snippet:

https://gist.github.com/nparsons08/b75ac9a5d84c0d5b87d1502a6c457c12

We’re now ready to start including Voxeet’s components and later, Stream Chat too. However, you should now have two missing imports from the file above. One is the Conference screen which we will create in just a second. The other is `utils/history.js`. 

By importing `history` in this way, we can pass it to the `Router` in the file above and it will operate identically to react-router v4’s `BrowserRouter`. We can also import it in our sagas (and anywhere in our code for that matter) to navigate programmatically without using `<Link />` or in areas where the `history` prop is inaccessible.

Here is the code for the history util:

https://gist.github.com/nparsons08/e834d53db9852ea7d476bdfacbbfc801

> **Note**: _We will still want to use the <Link /> component in our JSX code – but this allows us, for example, to redirect a user when they log in or log out, from within the authentication saga itself._

### Step 3: Initializing Voxeet

We’ll start by creating the Conference screen where the video calls themselves will take place. Create `Conference/Conference.js` inside of your `screens` directory, open it in your editor and paste in the following:

https://gist.github.com/nparsons08/cffc1cd348a32dc1a8346c7b2c7b2e70

You should now be able to go to `http://localhost:3000/test` to see a video call screen that is fully operational. The Voxeet components are fantastic. Out of the box, Voxeet provides full functionality to all of the features available by their SDK and APIs.

We are using the URL param `match.params.conferenceAlias` as defined in `App.js` to scope each conference to the URL – which also enables users to share the URL to invite other participants, much like Google Hangouts or Zoom. At this point, if you were to push your site up to Netlify, you can share the URL with a friend, and you will be able to conduct a full-featured video call! Note that you can use any random string after the `/` in the URL to set the conference id – but more on that later.

Right now we only have control over the styling of the Voxeet UI through overriding the default CSS and users will have randomly generated usernames and ids. So, let’s fix that. 

We can achieve much higher customization of the UI by passing in some additional props to the `ConferenceRoom` component, namely `attendeesChat` and `actionsButtons`. Both of these props should be passed either a function that returns a component, or the component itself. We can start by passing NOOP functions to these props as follows:

https://gist.github.com/nparsons08/db35e90c6c1e40130e7b92a81ecc0714

The components passed to these props will replace the chat drawer and the bottom actions bar, respectively. The `ConferenceRoom` will pass down the necessary state from Redux as props, allowing us to use custom components that will alter the state of the call. Furthermore, it will aid us in providing a custom chat UI, which we'll use Stream Chat for later in the tutorial!

With the above changes, you should now see the main wrapper of the Conference screen will be full-height and full-width in the browser, and the actions bar along the bottom will be gone. We can now start to build our custom call UI.

Inside of the `screens/Conference` directory, create a `components` directory and inside create a new file called `ActionsButtons.js`.

For now, we can leave this file empty as we will need some other components first that we can then utilize within the ActionButtons bar itself.

Back in `src/components` let’s create a new directory called Icons and an `Icon.js` file inside with the following code:

https://gist.github.com/nparsons08/68bd0bd39fc8e299636dbf96eaf4cc02

This file creates a re-usable `Icon` component that we can use to generate a set of consistent SVG icons. We can then use these icons just the same as any other react component. The `Icon` component also offers additional functionality – such as the ability to control the icon height/width via the `size` prop (while maintaining consistency if we set the `viewBox` correctly), as well as the ability to change the color with the `color` prop.

> **Note**: _Thanks to the withTheme HOC, we can pass color names that map directly to our styled-components theme – i.e., purple, red, error, placeholder, text, etc. meaning we can add more color options by adding values to styles/colors.js._

For the sake of speed and ease, we used [Material Icons](https://material.io/resources/icons/) – and converting them for use inside of our new Icon component is a breeze. To prevent repeating ourselves over and over, here is a link to a [ZIP file](https://stream-blog.s3.amazonaws.com/blog/wp-content/uploads/25a67b81dacb3a644d546a8295caaa04/icons.zip) containing the complete Icons folder that you can drop straight into you `components` directory.

Now that we have icons to use, we can create our `ActionButton` component that we will use inside of the `ActionsButtons.js` file we created earlier. Create a new `ActionButton.js` file inside of `screens/Conference/components` and paste in the following code:

https://gist.github.com/nparsons08/0b0d612a56b323180d14f308b5b8448a

And finally, we’re ready to create our custom actions bar! 

Back in `screens/Conference/components/ActionsButtons.js` we can now add the following:

https://gist.github.com/nparsons08/b5f97aa055b25372bead23a4a0224cb0

Currently, we are setting the `unreadCount` prop to fall back to `0` as we are not defining it anywhere just yet. We will pull this data off of our Redux store later once we have Stream Chat set up. 

All other props are provided by the Voxeet `ConferenceRoom` component and are used internally by the default `ActionsButtons` UI to change the state of buttons conditionally – so we will do the same. This state depends on parameters such as whether or not the user's mic or camera are disabled, if the chat drawer is open, etc. 

Note that we have left some of the default Voxeet views in the UI such as settings (for controlling the input/output devices and the `AttendeesList` for viewing all the active participants). 

These views are great out of the box, and small amounts of CSS can get them looking as we would want without building them out by hand. Currently, there is not a prop available to pass a custom settings view; however, should you wish to build the attendees list by hand, as we have with the `ActionsButtons`, you can pass a component in the same way to the `attendeesList` prop of the `ConferenceRoom`.

Now all that’s left to do is import our shiny new `ActionsButtons` into the `ConferenceRoom` as below:

https://gist.github.com/nparsons08/9771e90febfb4ab64302b39d8375e49a

Now in your browser, you should see our custom call UI along the bottom of the screen! You can hang up the call, disable your camera or microphone, share your screen with the other participants, change your I/O settings, view the list of participants and, last but not least, toggle the chat drawer. This will be empty for now, but we’ll come back to this shortly.

We are also going to want to override some of Voxeets CSS to have the Conference screen match the rest of our application. For the sake of brevity, below is the complete CSS file from our finished version - feel free to tweak any of the values you see fit.

Go to your `src/styles` folder and create a new directory called `css` – then create a new file inside called `voxeet.js` with the following styled-components CSS code:

https://gist.github.com/nparsons08/b6b1424e44397b1493f6efd1847ff4df

> **Note**: _By creating our CSS overrides in this way, we can write our CSS as we usually would in a standard .css file, but with access to our theme._

All that’s left to do now is to jump into `styles/global.js` and add the following two lines to import our overrides into the global CSS file:

https://gist.github.com/nparsons08/1897763255d8c003fe38afdcaccd2402

### Step 4: Setting up the Backend API & Authentication

Prior to setting up our Chat components, we will make things much easier for ourselves by first setting up the backend API and authentication for the app.

The backend for this project is simple, and Voxeet’s react components handle all of the Voxeet related backend functionality for you. All that we will need to do is pass in user data, which means we only need to generate a token for Stream Chat.

You can download the boilerplate here and follow along with the following section to get the backend and authentication flow working in your app. Alternatively, you can hit the Heroku button to immediately deploy the complete API code to Heroku or click here to view the finished API repo.

> **Note**: _You can click the Heroku Deploy button below to launch a Stream Chat trial using our prebuilt boilerplate API. Please take caution when using this, as the API does not enforce auth when hitting the /v1/token endpoint. If you would like to lock this down, we secure adding additional security measures to prevent authorized access._

<p>
<a href="https://heroku.com/deploy?template=https://github.com/nparsons08/stream-chat-boilerplate-api" target="_blank" rel="noopener noreferrer">
  <img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy Stream API">
</a>
</p>

> _**NOTE**: You can safely skip this next part and jump to building the authentication flow if you go the auto-deploy with Heroku route._

Let’s get to coding the backend! 

Fortunately, a lot of the work has already been completed through the provided boilerplate API. We need to make a few small changes to get it working how we want.

Outside of your frontend repository, run the following command in your terminal from whichever directory you want to store the API code in.

https://gist.github.com/nparsons08/23ebfc5df9ff74afdda6068609dc688d

Then, run `cd stream-voxeet-api && yarn` and open the `stream-chat-voxeet` directory in your editor. 

We’ll start by renaming `env.example` to `.env` and inserting our Stream key and secret that we saved earlier into the correct variables. Once you're done, it should look something like this:

https://gist.github.com/nparsons08/aebcb0edb135cfb7f4f5541da01c7773

Now, let’s hop back into the terminal and run `yarn dev` to spin up the development server. The repo uses nodemon to automatically refresh when you make a change whilst keeping the server alive.

Finally, we need to open `src/controllers/v1/token/token.action.js` and do a quick “find all” so that we can change all references to `data.email` to `data.username`. Your `token.action.js` file should now look like the following snippet:

https://gist.github.com/nparsons08/2a8f3f5d847de604e78da31dc44e300a

> **Note**: _By changing data.email to data.username we are changing the default behavior of authenticating via an email address, to using a simple username that we will build a login form for in just a second._

Optionally, you will see on line **28** the repo uses robohash to generate an avatar for the user. In our final version, we used ui-avatars.com as the images it generates are better suited to our design, but you can drop in any avatar generation URL here that you see fit.

And that’s it for the backend!

You can test that this is all working properly with Postman (or your REST client of choice is) by leaving the server running, and firing a POST request to `http://localhost:8080/v1/token` where the body is the following object:

https://gist.github.com/nparsons08/72a2e1cb51e954fadedb7e0ce3c2e418

If successful, you will see the following in the response body:

https://gist.github.com/nparsons08/64ee1d7b31dfd8d9565870e6b331e4cb

Awesome! We can now start to build our login form and authenticate users inside our app.

Leave your API server running for now, and head back to your frontend code in your editor. 

First up, let’s add a new env variable (in `.env`) named `REACT_APP_API_ENDPOINT` and set it’s value to `http://localhost:8080/v1` and then make sure we have rebooted the frontend by closing the dev server and running `yarn start` once again.

We’ll also need this small wrapper utility around Axios to make a request to our backend, save it in your project file in `src/utils/fetch.js`

https://gist.github.com/nparsons08/9f35c054523fe3503a0eb2f544ac047f
#### Building the Authentication Flow

Now that we’re all set up to generate tokens for our users, we’ll kick off the auth flow by creating a new directory inside of our `screens` directory called `Login` with a `Login.js` file inside of that with the following code:

https://gist.github.com/nparsons08/2d289e3a355383bd6f37e1efad46b5d7

In the `Login.js` file, we are importing a few components that we don’t have in our project just yet. The biggest of which is the `LoginForm` component which we will come back to in a second but first, here is the code for the `Text, `Input` and `Button` components we will need to build the form UI.

**Input**:

https://gist.github.com/nparsons08/09f6bbde2efaeae61fb25eb677302a24

**Button**:
 
https://gist.github.com/nparsons08/470b2b9b7e22a680cb33d7c02076607c

**Text**:

https://gist.github.com/nparsons08/45162770dc5cd5e392af3625358b8a67

Now we have all the pieces we need to build our `LoginForm` component using Formik and Yup which we installed at the start of the tutorial. 

Formik is an awesome library that allows us to easily build complex forms with React, and Yup provides a nice and powerful way to validate our input values. We won’t be utilizing the full potential of Formik in this tutorial, but should you decide to build upon this project after your done with the tutorial, this will set you up with a strong foundation for building any kind of form quickly and painlessly.

First, we’ll create a new directory inside of `src` called `forms` and inside of there create a file at this path `forms/LoginForm/LoginForm.js`.

https://gist.github.com/nparsons08/1f03a8fe57d18468599efd0cf61e8149

We start off by importing `Formik` and `Field` from `formik`. The `<Formik />` component will be the root element in our render function and we pass in our `onSubmit` handler from props as well as some initial values as defined in the getter function on line 24 and finally, our `this.renderForm` function as the child of the component.

> **Note**: _The initial values ensure that our inputs are always ‘controlled’.  If we don’t do this, the value will be undefined and react will throw an error to say we have switched the input from a non-controlled to a controlled state._

The `<Field />` component provided by Formik encapsulates all the functionality needed to pass the `onChange` handler down to our custom input component, along with error messages (if there are any). It also ensures that the value is updated in the correct key of the forms value store using the name prop, which we will set to `name=”username”` to match the key in the initial values.

We are also importing `validationSchema.js` – this is where Yup comes in. The syntax is pretty easy to grasp and not only are the docs easy to follow but Yup also provides useful helpers for validating emails, phone numbers, etc as well as the option to pass custom regex validators and easily set the error messages that will be passed into the input component if the conditions are not met.

Create the validation file in your `LoginForm` directory, next to the form component and paste in the following code:

https://gist.github.com/nparsons08/cd64d26c2f4e0485b7fa96d6128c747d

Now, back inside of `LoginForm.js` you’ll see that we are passing our Yup schema into Formik as the `validationSchema` prop. At this point, our form will be fully operational, but isn’t yet rendered to the DOM anywhere and doesn’t send it’s value to the backend just yet either.

Let’s quickly jump into `App.js` and update the file to render our login route.

https://gist.github.com/nparsons08/06c83025cf7822fe1ae7dad5cd8b84da

Now, when you navigate to http://localhost:3000/ you should see our login page rendered to the screen, complete with the login form. You can also fill out the form, click the button below and open your console to see the output from the form.

We now need a way to send our form data to the backend, retrieve our token, and authenticate the user before sending them to the Conference Screen. Time for some Redux magic! 🧙‍♂️

We’ll start out by creating our action creators and action types. You can find the relative snippets below, which will save inside of `data/auth` in our project.

**data/auth/types.js**:

https://gist.github.com/nparsons08/2dc8370e1bc625b4d0070565af6c902d

**data/auth/actions.js**:

https://gist.github.com/nparsons08/3c5b0fed12db259a544b8ec309d00249

> **Note**: _You’ll notice we are passing conferenceAlias as the second argument to the loginRequest action. This will be used later on when a user receives a link to join a call so that we can route them to the correct place once they have logged in._

Now, inside of `auth/sagas/index.js` add the following code:

https://gist.github.com/nparsons08/b1ac9acc6bb9ee923b6d62d3b6ba075f

Generators, used heavily in redux-saga, are a new feature of JS as of ES6 and work very similarly to async/await. They are initialized using the `function * ()` syntax and have a special keyword `yield` that works very much like `await` by “pausing” the function and waiting for the expression to resolve before continuing. 

`redux-saga` also provides some helpers like `takeEvery` (used in the above snippet) which will take an action type as the first argument and a saga as the second. Every time the action is fired, the saga will run.

So the next logical step is to build our `loginRequest` saga. Inside of `auth/sagas` create a new file, `loginRequest.js` and paste in the following code.

https://gist.github.com/nparsons08/ebd60d75e1eecc0e007b933c0e229ebd

Let’s quickly break down what’s going on here.

- We import the `all`, `call` and `put` helpers from redux-saga as well as `shortid` for generating the conference alias if we aren’t provided one by our login action.
- We then import `history` and `fetch` from our utils directory.
- We also import our `LOGIN` action type so we can fire a success action or error action later in the saga.
- Next, we define our saga and destructure its first and only argument, which will always be the action creator we fired (`loginRequest`), meaning we can pull the values off of the action and use them in our saga, in this case, the optional conference alias and the form data.
- We then open a try/catch block and do some simple formatting on the username, to make sure it is all lowercase and contains no spaces (we’re replacing them with ‘_’ using regex) before running our fetch call.
- We use the `call` helper and pass in fetch as the first argument. All following arguments will be passed through to fetch itself.
- Axios (and therefore our fetch util) returns the response body from the server as the `data` key, so we also destructure that to pull out the token and user object from the response.
- Next, we store these values in localStorage so we can persist the user across sessions (but more on this later when we get to our reducer) and then check if a `conferenceAlias` was present in the action data. If it wasn’t, we generate one using `shortid`.
- And finally, we use the `all` helper to run two saga helpers in parallel. The first is `put`, which will fire our success action for the reducer to consume and the second is `call` in which we pass `history.push` to navigate the user to the conference screen.
- We also use `put` in the catch block to fire the `LOGIN.ERROR` action and pass the message to the reducer.

Now that our login saga is ready to rock, we need to make sure we are actually running the saga so that it can listen for the `LOGIN.REQUEST` action. We will do this by using another saga helper (`fork`) in the root saga that we created when we initialized our Redux store.

Open `data/rootSaga.js` in your editor and amend it to look like the following snippet:

https://gist.github.com/nparsons08/670fbde9c11697b076ac2461b7b166b4

Next, create a `reducer.js` file inside of `data/auth` and add the following code to it:

https://gist.github.com/nparsons08/7def99e3e5a8e6271ce362bd3f1b62f3

The reducer will handle the `LOGIN.SUCCESS` action and save the users token and profile data to the Redux store. We’re passing in some initial state with the defaults set to pull the values from `localStorage` – if they aren’t present they will just return null and therefore leave the app in an unauthenticated state ready for login.

We can now use this data in our components to create a wrapper around the Conference screen route to protect it from unauthenticated users. In your `src/containers` directory, create a new file called `AuthedRoute.js` and past in the following snippet:

https://gist.github.com/nparsons08/10912f5a8a135097df81c65125d7a16f

The above component handles protecting our route from unauthenticated users as well as showing a loading screen if the auth request is in progress. It will also pass the `conferenceAlias` parameter to `location.state` so that if a user tries to access a conference without logging in, it will store the id so we can redirect them by checking for the value in the location state and passing it through to the action creator that we set up previously.

For the LoadingScreen add the following file to `screens/LoadingScreen.js`:

https://gist.github.com/nparsons08/4fe40110b8b6cca685f227c43464abc9

We are also using `reselect` here to pull values out of the Redux store. `reselect` memoizes the values from our store to reduce accidental or unnecessary re-renders of our components if the values haven’t changed. But, we haven’t defined these selectors yet. Back in `data/auth` create a new file called `selectors.js` and add the following:

https://gist.github.com/nparsons08/5aa572f7d41cdf3831af89bb23cc0b0c

Now to implement our `AuthedRoute`, jump back into your `App.js` file, import it and update the route definition for the conference screen to use `AuthedRoute` instead of React Routers `<Route />`.

https://gist.github.com/nparsons08/e9012b22678f17447b911cc4e6d292aa

Finally, let’s go back into our `screens/Login/Login.js` file and make the following changes:

https://gist.github.com/nparsons08/c1fc1a8ff7e3cf91b3cbdd69af09e87c

We added some getter functions for formatting the user's name and retrieving the `conferenceAlias` from the location state. We also connected our component to the Redux store so that we can access our store as well as mapping our `loginRequest` function to props so we can fire it in the `handleLogin` method. 

Lastly, we added a new method to our class, `renderWelcome` which renders a slightly different UI that shows the uses avatar and a “Start Video Call” button to launch a call. We conditionally call either that or `renderLogin` in our render function depending on whether the user is authenticated or not. 

Now that our backend is in place and our Login screen is fully operational, we can finish up our app by building our chat implementation with Stream! 🎉

![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/b9cec86f1c28cc04b94bee1efbcd499f/image2.png?auto=compress%2Cformat&ixlib=php-1.2.1)

### Step 5: Stream Chat

If you have clicked the chat button in our call UI we built earlier, you will have no doubt noticed that opening the drawer will move the videos over to the left, but the drawer itself will not be visible. So now is as good a time as any to start integrating Stream Chat and building the custom drawer that we can pass into our `ConferenceRoom`.

First up, you will need to add the following `Portal` util, built using Reacts internal Portal implementation. If you haven’t used Portals before, they essentially allow you to define a component anywhere in the tree but have it rendered to the DOM anywhere outside of its current parent hierarchy. We will use the Portal to render a chat drawer inside of the body – above the rest of the app. 

https://gist.github.com/nparsons08/e5e91b2c4fe296370c7e13210f1286b7

We will also need a couple more action creators to handle toggling our drawer from outside of our `ActionsButtons` component and setting the unread count in Redux. Create a new directory called `chat` inside of your `src/data` directory and place the following three files inside that contain our Action Types, Action Creators and Selectors for pulling the values out of Redux.

https://gist.github.com/nparsons08/fbb117bf97fada09a09748302e53252c

https://gist.github.com/nparsons08/1594e6d30fa833d9e27452414980c22f

https://gist.github.com/nparsons08/6528df370e482909281a099ac4e8ee4c

Last, we will need to create our `chat` reducer and import it into our root reducer also. Below is the code for the reducer, followed by a snippet showing the root reducer.

https://gist.github.com/nparsons08/68efe716d4fd8c2c4447ad597420bc22

https://gist.github.com/nparsons08/71c61be6eb939ca7f555fcdfab2a59ec

Now, on to our chat drawer!

In our `screens/Conference` directory, let's make a new directory called `containers` and create a new directory inside of that called `AttendeesChat`. Inside, we’ll create `AttendeesChat.js` and place the following code inside:

https://gist.github.com/nparsons08/d1f8cc286bd011e6af74481be88547ec

There is quite a lot going on here – so here is a breakdown. 

First, in our constructor, we define our default state and also set `this.chatClient` to contain our `StreamChat` instance and pass it the `.env` variables we set at the beginning of the tutorial. Then, in `componentDidMount` we call `this.chatClient.setUser` and pass in an object representing our `user` which we are pulling into props using our Redux selectors.

We then initialize the chat channel using `this.chatClient.channel()` and pass in the “messaging” config parameter, along with the `conferenceAlias` from our URL parameter so that we can scope the chat channel to the current conference and finally, call `this.setState` to store our channel in our components state for later.

https://gist.github.com/nparsons08/28e70b2ac2a1a9a90110dfe2e2f6e394

Next, in `componentDidUpdate` we run a comparison check that will run when our channel is first initialized, by comparing the previous state value to the current one and checking our channel exists. If the condition is truthy, we run `this.init()` and in turn, calls `await this.state.channel.watch()` which subscribes the channel to future updates.

https://gist.github.com/nparsons08/ed40bc0a88158066a8a685396a75919e

We also initialize a listener here, that fires on the `message.new` event. Every time a new message is sent in the channel, the callback will run and updates our unread count using our `setUnreadCount` action creator.

Next, in our render function, we render the `stream-chat-react` components that will power our UI and handle a lot of the state management for us. 

https://gist.github.com/nparsons08/2aa4bfc38c56395a9d3364867624b360

We wrap everything with the `Chat` component and pass in `this.chatClient` to the client prop. Inside of this, we render the `Channel` component to which we pass our `channel` from `this.state`, followed by `Window` which will wrap the visual aspects of our Chat UI. Then, Inside of our `Window` we render the `ChatHeader`, `MessageList`, `MessageInput`. 

Back inside of `screens/Conference/components` create `ChatHeader.js` and add the following snippet:

https://gist.github.com/nparsons08/3dce61ce81c0396766b1fc1481ff1626

And that’s everything we need for a fully operational chat UI!

But we aren’t finished just yet...

We need to include the CSS file from `stream-chat-react`. The default file exported from the library is, of course, responsive. However, this presents some issues due to the fact we are rendering the Chat components inside of a 375px wide wrapper div.

Because CSS media queries react to the size of the window and not the size of the parent div, properties such as `padding` and `max-width` will be incorrect – using the values it would for the full viewport regardless of the container size. To fix this, we’ll strip out the media queries so that we only use the mobile-sized definitions.

https://gist.github.com/nparsons08/df20fae80b04874003507c91368cdb38

Place the above file in your `public` directory, then in `public/index.html` you can import the file like so:

https://gist.github.com/nparsons08/8ce033d421f5ceb543a9dcc34b775437

We also need to jump back into our `ActionsButtons.js` component we made for our custom call UI along the bottom of the screen and connect it to our redux store so we can retrieve the unread count and show the notification badge accordingly:

https://gist.github.com/nparsons08/fd964689f0845444cf687c554e3397e1

## Final Thoughts

In this tutorial, you’ve successfully built a fully functioning video-conferencing application. For more information on Voxeet, head over to their website at https://voxeet.com. 

If you’re interested in adding chat to your application, Stream Chat has you covered – with React Components (that we used in this tutorial), as well as SDKs and libraries for JS, iOS, Android, and most other popular programming languages.

Happy coding! ✌️

[![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/c1a49333ef28a0a4c92870d66988e444/dolbybtm-cta.png)](https://events.dolby.com/dolby-stream/getstreamio-product "![](https://stream-blog-v2.imgix.net/blog/wp-content/uploads/c1a49333ef28a0a4c92870d66988e444/dolbybtm-cta.png)")