Build a Video Chat with React Hooks
Tags: javascript, react, twilio, video
Published: 2019-10-14 22:52:38

---
title: Build a Video Chat with React Hooks
published: true
description: In this post we are going to build a video chat application using Twilio Video and React with only functional components, using the useState, useCallback, useEffect and useRef hooks.
tags: javascript, react, twilio, video
cover_image: https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/IJn0tLQyaYFOpy_DdqV5cF6J5CWT6mslruhTKouesj1o3D.width-808.png
canonical_url: https://www.twilio.com/blog/video-chat-react-hooks
---

We've seen [a video chat built in React on this blog](https://www.twilio.com/blog/2018/03/video-chat-react.html) before but since then, in version 16.8, React released [Hooks](https://reactjs.org/docs/hooks-intro.html). Hooks let you use state or other React features inside functional components instead of writing a class component.

In this post we are going to build a video chat application using [Twilio Video](https://www.twilio.com/docs/video/javascript) and React with only functional components, using the `useState`, `useCallback`, `useEffect` and `useRef` hooks.

## What you'll need

To build this video chat application you will need the following:

*   [Node.js and npm installed](https://nodejs.org/en/)
*   A Twilio account (sign up for a [free Twilio account](https://www.twilio.com/try-twilio) here)

Once you've got all that, we can prepare our development environment.

## Getting started

So we can get straight to the React application, we can start with [the React and Express starter app](https://github.com/philnash/react-express-starter/tree/twilio) [I created](https://www.twilio.com/blog/react-app-with-node-js-server-proxy). Download or clone the starter app's ["twilio" branch](https://github.com/philnash/react-express-starter/tree/twilio), change into the new directory and install the dependencies:

```bash
git clone -b twilio git@github.com:philnash/react-express-starter.git twilio-video-react-hooks
cd twilio-video-react-hooks
npm install
```

Copy the `.env.example` file to `.env`

```bash
cp .env.example .env
```

Run the application to make sure everything is working as expected:


```bash
npm run dev
```

You should see this page load in the browser:

![The initial page shows the React logo and a form](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/9_dV_pwN_d1AOtmFhZRP2dil1XwDxKiLSr875kXALxNk7k.width-500.png)

### Preparing Twilio credentials

To connect to Twilio video we will need some credentials. From your [Twilio console](https://www.twilio.com/console) copy your Account SID and enter it in the `.env` file as the `TWILIO_ACCOUNT_SID`.

You will also need an API key and secret, you can create these under the [Programmable Video Tools in your console](https://www.twilio.com/console/video/project/api-keys). Create a key pair and add the SID and Secret as `TWILIO_API_KEY` and `TWILIO_API_SECRET` to the `.env` file.

### Adding some style

We're not going to concern ourselves with CSS for this post, but let's add some so the result doesn't look awful! Grab [the CSS from this URL](https://raw.githubusercontent.com/philnash/twilio-video-react-hooks/master/src/App.css) and replace the contents of `src/App.css` with it.

Now we're ready to start building.

## Planning our components

Everything will start in our `App` component where we can lay out a header and footer for the app as well as a `VideoChat` component. Within the `VideoChat` component we'll want to show a `Lobby` component where the user can enter their name and the room they want to join. Once they have entered those details we'll replace the `Lobby` with a `Room` component that will handle connecting to the room and displaying the participants in the video chat. Finally, for each participant in the room we will render a `Participant` component that will handle displaying their media.

## Building the components

### The App component

Open up `src/App.js`, there's a lot of code here from the initial example app that we can remove. Also, the `App` component is a class based component. We said we'd build the entire app with functional components, so we better change that.

From the imports, remove `Component` and the import of the logo.svg. Replace the entire App class with a function that renders our application skeleton. The whole file should look like this:

```jsx
import React from 'react';
import './App.css';

const App = () => {
  return (
    <div className="app">
      <header>
        <h1>Video Chat with Hooks</h1>
      </header>
      <main>
        <p>VideoChat goes here.</p>
      </main>
      <footer>
        <p>
          Made with{' '}
          <span role="img" aria-label="React">
            ⚛
          </span>{' '}
          by <a href="https://twitter.com/philnash">philnash</a>
        </p>
      </footer>
    </div>
  );
};

export default App;
```

### The VideoChat component

This component is going to show a lobby or a room based on whether the user has entered a username and room name. Create a new component file `src/VideoChat.js` and start it off with the following boilerplate:

```jsx
import React from 'react';

const VideoChat = () => {
  return <div></div> // we'll build up our response later
};

export default VideoChat;
```

The `VideoChat` component is going to be the top level component for handling the data about the chat. We're going to need to store a username for the user that is joining the chat, a room name for the room they are going to connect to, and their [access token](https://www.twilio.com/docs/video/tutorials/user-identity-access-tokens) once it has been fetched from the server. We will be building up a form to input some of this data in the next component.

With React Hooks we use the [`useState` hook](https://reactjs.org/docs/hooks-reference.html#usestate) to store this data.

#### useState

`useState` is a function that takes a single argument, the initial state, and returns an array containing the current state and a function to update that state. We'll destructure that array to give us two distinct variables like `state` and `setState`. We're going to use `setState` to track the username, room name and token within our component.

Start by importing `useState` from react and set up states for the username, room name and token:

```jsx
import React, { useState } from 'react';

const VideoChat = () => {
  const [username, setUsername] = useState('');
  const [roomName, setRoomName] = useState('');
  const [token, setToken] = useState(null);

  return <div></div> // we'll build up our response later
};
```

Next we need two functions to handle updating the `username` and `roomName` when the user enters them in their respective input elements.

```jsx
import React, { useState } from 'react';

const VideoChat = () => {
  const [username, setUsername] = useState('');
  const [roomName, setRoomName] = useState('');
  const [token, setToken] = useState(null);

  const handleUsernameChange = event => {
    setUsername(event.target.value);
  };

  const handleRoomNameChange = event => {
    setRoomName(event.target.value);
  };

  return <div></div> // we'll build up our response later
};
```

While this will work, we can optimise our component using another React hook here; `useCallback`

#### useCallback

Every time this function component is called the `handleXXX` functions are redefined. They need to be part of the component because they rely on the `setUsername` and `setRoomName` functions, but they will be the same every time. `useCallback` is a React hook that allows us to [memoize](https://en.wikipedia.org/wiki/Memoization) the functions. That is, if they are the same between function invocations, they won't get redefined.

`useCallback` takes two arguments, the function to be memoized and an array of the function's dependencies. If any of the function's dependencies change, that implies the memoized function is out of date and the function is then redefined and memoized again.

In this case, there are no dependencies to these two functions, so an empty array will suffice (`setState` functions from the `useState` hook are deemed to be constant within the function). Rewriting this function we need to add `useCallback` to the import at the top of the file and then wrap each of these functions.

```jsx
import React, { useState, useCallback } from 'react';

const VideoChat = () => {
  const [username, setUsername] = useState('');
  const [roomName, setRoomName] = useState('');
  const [token, setToken] = useState(null);

  const handleUsernameChange = useCallback(event => {
    setUsername(event.target.value);
  }, []);

  const handleRoomNameChange = useCallback(event => {
    setRoomName(event.target.value);
  }, []);

  return <div></div> // we'll build up our response later
};
```

When the user submits the form we want to send the username and room name to the server to exchange for an access token we can use to enter the room. We'll create that function in this component too.

We'll use the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to send the data as JSON to the endpoint, receive and parse the response, then use `setToken` to store the token in our state. We'll also wrap this function with `useCallback` too, but in this case the function will depend on the `username` and `roomName`, so we add those as the dependencies to `useCallback`.

```jsx
  const handleRoomNameChange = useCallback(event => {
    setRoomName(event.target.value);
  }, []);

  const handleSubmit = useCallback(async event => {
    event.preventDefault();
    const data = await fetch('/video/token', {
      method: 'POST',
      body: JSON.stringify({
        identity: username,
        room: roomName
      }),
      headers: {
        'Content-Type': 'application/json'
      }
    }).then(res => res.json());
    setToken(data.token);
  }, [username, roomName]);

  return <div></div> // we'll build up our response later
};
```

For the final function in this component we'll add a logout functionality. This will eject the user from a room and return them to the lobby. To do so we will set the token to `null`. Once again, we wrap this up in `useCallback` with no dependencies.

```jsx
  const handleLogout = useCallback(event => {
    setToken(null);
  }, []);

  return <div></div> // we'll build up our response later
};
```

This component is mostly orchestrating the components below it, so there's not much to render until we have created those components. Let's create the Lobby component that renders the form that asks for a username and room name next.

### The Lobby component

Create a new file in `src/Lobby.js`. This component doesn't need to store any data as it will pass all events up to its parent, the VideoChat component. When the component is rendered it will be passed the `username` and `roomName` as well as the functions to handle changes to each and handle submitting the form. We can destructure those props to make it easier to use them later.

The main job of the `Lobby` component is to render the form using those props, like this:

```jsx
import React from 'react';

const Lobby = ({
  username,
  handleUsernameChange,
  roomName,
  handleRoomNameChange,
  handleSubmit
}) => {
  return (
    <form onSubmit={handleSubmit}>
      <h2>Enter a room</h2>
      <div>
        <label htmlFor="name">Name:</label>
        <input
          type="text"
          id="field"
          value={username}
          onChange={handleUsernameChange}
          required
        />
      </div>

      <div>
        <label htmlFor="room">Room name:</label>
        <input
          type="text"
          id="room"
          value={roomName}
          onChange={handleRoomNameChange}
          required
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
};

export default Lobby;
```

Let's update the `VideoChat` component to render the `Lobby` unless we have a `token`, otherwise we'll render the `username`, `roomName` and `token`. We'll need to import the `Lobby` component at the top of the file and render some JSX at the bottom of the component function:

```jsx
import React, { useState, useCallback } from 'react';
import Lobby from './Lobby';

const VideoChat = () => {
  // ...

  const handleLogout = useCallback(event => {
    setToken(null);
  }, []);
  
  let render;
  if (token) {
    render = (
      <div>
        <p>Username: {username}</p>
        <p>Room name: {roomName}</p>
        <p>Token: {token}</p>
      </div>
    );
  } else {
    render = (
      <Lobby
         username={username}
         roomName={roomName}
         handleUsernameChange={handleUsernameChange}
         handleRoomNameChange={handleRoomNameChange}
         handleSubmit={handleSubmit}
      />
    );
  }
  return render;
};
```

To get this to show on the page we also need to import the `VideoChat` component into the `App` component and render it. Open `src/App.js` again and make the following changes:

```jsx
import React from 'react';
import './App.css';
import VideoChat from './VideoChat';

const App = () => {
  return (
    <div className="app">
      <header>
        <h1>Video Chat with Hooks</h1>
      </header>
      <main>
        <VideoChat />
      </main>
      <footer>
        <p>
          Made with{' '}
          <span role="img" aria-label="React">
            ⚛️
          </span>{' '}
          by <a href="https://twitter.com/philnash">philnash</a>
        </p>
      </footer>
    </div>
  );
};

export default App;
```

Make sure the app is still running (or restart it with `npm run dev`) and open it up in the browser and you will see a form. Fill in a username and room name and submit and the view will change to show you the names you chose plus the token retrieved from the server.

![Fill in the form form and submit and you will see the username, room name and token on the page.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/D8LkWJVbNeQR_xBVt4rO5NVKKOBlu6JY_h0u--To9rEYLj.width-500.png)

### The Room component

Now that we've added a username and room name to the application we can use them to join a Twilio Video chat room. To work with the Twilio Video service, we'll need the JS SDK, install it with:

```bash
npm install twilio-video --save
```

Create a new file in the `src` directory called `Room.js`. Start it off with the following boilerplate. We're going to be using the Twilio Video SDK in this component as well as the `useState` and `useEffect` hooks. We're also going to get `roomName`, `token` and `handleLogout` as props from the parent `VideoChat` component:

```jsx
import React, { useState, useEffect } from 'react';
import Video from 'twilio-video';

const Room = ({ roomName, token, handleLogout }) => {

});

export default Room;
```

The first thing that the component will do is connect to the Twilio Video service using the token and roomName. When we connect we will get a `room` object, which we will want to store. The room also includes a list of participants which will change over time, so we'll store them too. We'll use `useState` to store these, the initial values will be `null` for the room and an empty array for the participants:

```jsx
const Room = ({ roomName, token, handleLogout }) => {
  const [room, setRoom] = useState(null);
  const [participants, setParticipants] = useState([]);
});
```

Before we get to joining the room, let's render something for this component. We'll map over the participants array to show the identity of each participant and also show the identity of the local participant in the room:

```jsx
const Room = ({ roomName, token, handleLogout }) => {
  const [room, setRoom] = useState(null);
  const [participants, setParticipants] = useState([]);

  const remoteParticipants = participants.map(participant => (
    <p key={participant.sid}>participant.identity</p>
  ));

  return (
    <div className="room">
      <h2>Room: {roomName}</h2>
      <button onClick={handleLogout}>Log out</button>
      <div className="local-participant">
        {room ? (
          <p key={room.localParticipant.sid}>{room.localParticipant.identity}</p>
        ) : (
          ''
        )}
      </div>
      <h3>Remote Participants</h3>
      <div className="remote-participants">{remoteParticipants}</div>
    </div>
  );
});
```

Let's update the `VideoChat` component to render this `Room` component in place of the placeholder information we had earlier.

```jsx
import React, { useState, useCallback } from 'react';
import Lobby from './Lobby';
import Room from './Room';

const VideoChat = () => {
  // ...

  const handleLogout = useCallback(event => {
    setToken(null);
  }, []);
  
  let render;
  if (token) {
    render = (
      <Room roomName={roomName} token={token} handleLogout={handleLogout} />
    );
  } else {
    render = (
      <Lobby
         username={username}
         roomName={roomName}
         handleUsernameChange={handleUsernameChange}
         handleRoomNameChange={handleRoomNameChange}
         handleSubmit={handleSubmit}
      />
    );
  }
  return render;
};
```

Running this in the browser will show the room name and the log out button, but no participant identities because we haven't connected and joined the room yet.

![When you submit the form now, you will see the room name, "Awesome Room" in this case, and a space for remote participants.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/ny23lIPUsvH-OvtaQ3KBfuyI0OsTCEgKFU4jk-9r4qA4vn.width-500.png)

We have all the information we need to join a room, so we should trigger the action to connect on the first render of the component. We also want to exit the room once the component is destroyed (no point keeping a WebRTC connection around in the background). These are both side effects.

With class based components, this is where you would use the `componentDidMount` and `componentWillUnmount` lifecycle methods. With React hooks, we'll be using the [](https://reactjs.org/docs/hooks-effect.html)[useEffect](https://reactjs.org/docs/hooks-effect.html) [hook](https://reactjs.org/docs/hooks-effect.html).

#### useEffect

`useEffect` is a function that takes a method and runs it once the component has rendered. When our component loads we want to connect to the video service, we'll also need functions we can run whenever a participant joins or leaves the room to add and remove participants from the state respectively.

Let's start to build up our hook by adding this code before the JSX in `Room.js`:

```jsx
  useEffect(() => {
    const participantConnected = participant => {
      setParticipants(prevParticipants => [...prevParticipants, participant]);
    };
    const participantDisconnected = participant => {
      setParticipants(prevParticipants =>
        prevParticipants.filter(p => p !== participant)
      );
    };
    Video.connect(token, {
      name: roomName
    }).then(room => {
      setRoom(room);
      room.on('participantConnected', participantConnected);
      room.on('participantDisconnected', participantDisconnected);
      room.participants.forEach(participantConnected);
    });
  });
```

This uses the `token` and `roomName` to connect to the Twilio Video service. When the connection is complete we set the room state, set up a listener for other participants connecting or disconnecting and loop through any existing participants adding them to the participants array state using the `participantConnected` function we wrote earlier.

This is a good start, but if we remove the component we'll still be connected to the room. So we need to clean up after ourselves as well.

If we return a function from the callback we pass to `useEffect`, it will be run when the component is unmounted. When a component that uses `useEffect` is rerendered, this function is also called to clean up the effect before it is run again.

Let's return a function that stops all the local partipant's tracks and then disconnects from the room, if the local participant is connected:

```jsx
    Video.connect(token, {
      name: roomName
    }).then(room => {
      setRoom(room);
      room.on('participantConnected', participantConnected);
      room.participants.forEach(participantConnected);
    });

    return () => {
      setRoom(currentRoom => {
        if (currentRoom && currentRoom.localParticipant.state === 'connected') {
          currentRoom.localParticipant.tracks.forEach(function(trackPublication) {
            trackPublication.track.stop();
          });
          currentRoom.disconnect();
          return null;
        } else {
          return currentRoom;
        }
      });
    };
  });
```

Note that here we use the callback version of the `setRoom` function that we got from `useState` earlier. If you pass a function to `setRoom` then it will be called with the previous value, in this case the existing room which we'll call `currentRoom`, and it will set the state to whatever you return.

We're not done yet though. In its current state this component will exit a joined room and reconnect to it every time it is re-rendered. This is not ideal, so we need to tell it when it should clean up and run the effect again. Much like `useCallback` we do this by passing an array of variables that the effect depends on. If the variables have changed, we want to clean up first, then run the effect again. If they haven't changed there's no need to run the effect again.

Looking at the function we can see that were the `roomName` or `token` to change we'd expect to connect to a different room or as a different user. Let's pass those variables as an array to `useEffect` as well:

```jsx
    return () => {
      setRoom(currentRoom => {
        if (currentRoom && currentRoom.localParticipant.state === 'connected') {
          currentRoom.localParticipant.tracks.forEach(function(trackPublication) {
            trackPublication.track.stop();
          });
          currentRoom.disconnect();
          return null;
        } else {
          return currentRoom;
        }
      });
    };
  }, [roomName, token]);
```

Note that we have two callback functions defined within this effect. You might think these should be wrapped in `useCallback` as we did earlier, but that's not the case. Since they are part of the effect they will only be run when the dependencies update. You also can't use hooks within callback functions, [they must be used directly within components or a custom hook](https://reactjs.org/docs/hooks-rules.html).

We're mostly done with this component. Let's check that it's working so far, reload the application and enter a username and room name. You should see your identity appear as you join the room. Clicking the logout button will take you back to the lobby.

![Now when you submit the form you will see everything before, plus your own identity.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/-s0fPI0vKOtlqYjdw_VagorkAkoIuwGq5Oj8MYH3VvprCW.width-500.png)

The final piece of the puzzle is to render the participants in the video call, adding their video and audio to the page.

### The Participant component

Create a new component in `src` called `Participant.js`. We'll start with the usual boilerplate, although in this component we're going to use three hooks, `useState` and `useEffect`, which we've seen, and `useRef`. We'll also be passing a `participant` object in the props and keeping track of the participant's video and audio tracks with `useState`:

```jsx
import React, { useState, useEffect, useRef } from 'react';

const Participant = ({ participant }) => {
  const [videoTracks, setVideoTracks] = useState([]);
  const [audioTracks, setAudioTracks] = useState([]);
};

export default Participant;
```

When we get a video or audio stream from our participant, we're going to want to attach it to a `<video>` or `<audio>` element. As JSX is declarative, we don't get direct access to the DOM (Document Object Model), so we need to get a reference to the HTML element some other way.

React provides access to the DOM via [refs](https://reactjs.org/docs/refs-and-the-dom.html) and the [](https://reactjs.org/docs/hooks-reference.html#useref)[useRef](https://reactjs.org/docs/hooks-reference.html#useref) [hook](https://reactjs.org/docs/hooks-reference.html#useref). To use refs we declare them up front then reference them within the JSX. We create our refs using the `useRef` hook, before we render anything:

```jsx
const Participant = ({ participant }) => {
  const [videoTracks, setVideoTracks] = useState([]);
  const [audioTracks, setAudioTracks] = useState([]);

  const videoRef = useRef();
  const audioRef = useRef();
 });
```

For now, let's return our JSX that we want. To hook up the JSX element to the ref we use the `ref` attribute.

```jsx
const Participant = ({ participant }) => {
  const [videoTracks, setVideoTracks] = useState([]);
  const [audioTracks, setAudioTracks] = useState([]);

  const videoRef = useRef();
  const audioRef = useRef();

  return (
    <div className="participant">
      <h3>{participant.identity}</h3>
      <video ref={videoRef} autoPlay={true} />
      <audio ref={audioRef} autoPlay={true} muted={true} />
    </div>
  );
 });
```

I've also set the attributes of the `<video>` and `<audio>` tags to autoplay (so that they play as soon as they have a media stream) and muted (so that I don't deafen myself with feedback during testing, you'll thank me for this if you ever make this mistake)

This component doesn't do much yet as we need to use some effects. We'll actually use the `useEffect` hook three times in this component, you'll see why soon.

The first `useEffect` hook will set the video and audio tracks in the state and set up listeners to the participant object for when tracks are added or removed. It will also need to clean up and remove those listeners and empty the state when the component is unmounted.

In our first `useEffect` hook, we'll add two functions that will run either when a track is added or removed from the participant. These functions both check whether the track is an audio or video track and then add or remove it from the state using the relevant state function.

```jsx
  const videoRef = useRef();
  const audioRef = useRef();

  useEffect(() => {
    const trackSubscribed = track => {
      if (track.kind === 'video') {
        setVideoTracks(videoTracks => [...videoTracks, track]);
      } else {
        setAudioTracks(audioTracks => [...audioTracks, track]);
      }
    };

    const trackUnsubscribed = track => {
      if (track.kind === 'video') {
        setVideoTracks(videoTracks => videoTracks.filter(v => v !== track));
      } else {
        setAudioTracks(audioTracks => audioTracks.filter(a => a !== track));
      }
    };

    // more to come
```

Next we use the participant object to set the initial values for the audio and video tracks, set up listeners to the trackSubscribed and trackUnsubscribed events using the functions we just wrote and then do the cleanup in the returned function:

```jsx
  useEffect(() => {
    const trackSubscribed = track => {
      // implementation
    };

    const trackUnsubscribed = track => {
      // implementation
    };

    setVideoTracks(Array.from(participant.videoTracks.values()));
    setAudioTracks(Array.from(participant.audioTracks.values()));

    participant.on('trackSubscribed', trackSubscribed);
    participant.on('trackUnsubscribed', trackUnsubscribed);

    return () => {
      setVideoTracks([]);
      setAudioTracks([]);
      participant.removeAllListeners();
    };
  }, [participant]);

  return (
    <div className="participant">
```

Note that the hook only depends on the `participant` object and won't be cleaned up and re-run unless the participant changes.

We also need a `useEffect` hook to attach the video and audio tracks to the DOM, I'll show just one of them here, the video version, but the audio is the same if you substitute video for audio. The hook will get the first video track from the state and, if it exists, attach it to the DOM node we captured with a ref earlier. You can refer to the current DOM node in the ref using `videoRef.current`. If we attach the video track we'll also need to return a function to detach it during cleanup.

```jsx
  }, [participant]);

  useEffect(() => {
    const videoTrack = videoTracks[0];
    if (videoTrack) {
      videoTrack.attach(videoRef.current);
      return () => {
        videoTrack.detach();
      };
    }
  }, [videoTracks]);

  return (
    <div className="participant">
```

Repeat that hook for `audioTracks` and we're ready to render our `Participant` component from the `Room` component. Import the `Participant` component at the top of the file and then replace the paragraphs which displayed the identity with the component itself.

```jsx
import React, { useState, useEffect } from 'react';
import Video from 'twilio-video';
import Participant from './Participant';

// hooks here

  const remoteParticipants = participants.map(participant => (
    <Participant key={participant.sid} participant={participant} />
  ));

  return (
    <div className="room">
      <h2>Room: {roomName}</h2>
      <button onClick={handleLogout}>Log out</button>
      <div className="local-participant">
        {room ? (
          <Participant
            key={room.localParticipant.sid}
            participant={room.localParticipant}
          />
        ) : (
          ''
        )}
      </div>
      <h3>Remote Participants</h3>
      <div className="remote-participants">{remoteParticipants}</div>
    </div>
  );
});
```

Now reload the app, join a room and you'll see yourself on screen. Open another browser and join the same room and you'll see yourself twice. Hit the logout button and you'll be back in the lobby.

![Success! You should now see yourself in a video chat with yourself.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/5MI8MqOtM2GXLYQZ9bDUB_NspMUimBl05HPZ4ez-pMdRHD.width-500.png)

## Conclusion

Building with Twilio Video in React takes a bit more work because there are all sorts of side effects to deal with. From making a request to get the token, connecting to the Video service and manipulating the DOM to connect `<video>` and `<audio>` elements, there's quite a bit to get your head around. In this post we've seen how to use `useState`, `useCallback`, `useEffect` and `useRef` to control these side effects and build our app using just functional components.

Hopefully this helps your understanding of both Twilio Video and React Hooks. All the source code of this application [is available on GitHub](https://github.com/philnash/twilio-video-react-hooks) for you to take apart and put back together.

For further reading on React Hooks take a look at [the official documentation](https://reactjs.org/docs/hooks-intro.html), which is very thorough, this visualisation on [thinking in hooks](https://wattenberger.com/blog/react-hooks) and check out [Dan Abramov's deep dive into `useEffect`](https://overreacted.io/a-complete-guide-to-useeffect/) (it's a long post, but worth it, I promise).

If you want to learn more about building with Twilio Video, check out these posts on [switching cameras during a Video chat](https://www.twilio.com/blog/2018/06/switching-cameras-twilio-video-chat.html) or adding [screen-sharing to your Video chat](https://www.twilio.com/blog/2018/01/screen-sharing-twilio-video.html).

If you build these, or any other cool video chat features, out in React let me know in the comments, [on Twitter](https://twitter.com/philnash) or over email at [philnash@twilio.com](mailto:philnash@twilio.com).