Criar um chat por vídeo com React Hooks

October 09, 2019
Escrito por
Phil Nash
Twilion

Criar um chat por vídeo com React Hooks

Vimos antes um chat por vídeo integrado no React neste blog, mas, desde então, na versão 16.8, o React lançou os Hooks. Hooks permitem que você use o estado ou outros recursos do React dentro de componentes funcionais em vez de escrever um componente de classe.

Nesta publicação, vamos criar um aplicativo de chat por vídeo usando o Twilio Video e React apenas com componentes funcionais, usando os hooks useStateuseCallbackuseEffect e useRef.

De que você vai precisar

Para criar este aplicativo de chat por vídeo, você vai precisar do seguinte:

Assim que você tiver tudo isso, podemos preparar nosso ambiente de desenvolvimento.

Primeiros passos

Para que possamos ir direto ao aplicativo React, podemos começar com o aplicativo React e Express inicial que criei. Baixe ou clone o branch "twilio" do aplicativo inicial, mude para o novo diretório e instale as dependências:

git clone https://github.com/philnash/twilio-video-react-hooks.git
cd twilio-video-react-hooks
npm install

Copie o arquivo .env.example em .env

cp .env.example .env

Execute o aplicativo para garantir que tudo esteja funcionando como esperado:

npm run dev

Você deve ver essa página carregar no navegador:

A página inicial mostra o logotipo do React e um formulário

Preparar as credenciais da Twilio

Para se conectar ao Twilio Video, precisaremos de algumas credenciais. No seu Console da Twilio, copie o Account SID (SID da conta) e insira-o no arquivo .env como o TWILIO_ACCOUNT_SID.

Você também precisará de uma chave de API e um segredo, que podem ser criados nas Programmable Video Tools (Ferramentas Programmable Video) em seu console. Crie um par de chaves e adicione o SID e o segredo como TWILIO_API_KEY e TWILIO_API_SECRET no arquivo .env.

Adicionar um pouco de estilo

Não vamos nos preocupar com CSS para este post, mas vamos adicionar alguns para que o resultado não pareça horrível! Pegue o CSS neste URL e substitua o conteúdo de src/App.css por ele.

Agora, estamos prontos para começar a construir.

Planejar nossos componentes

Tudo começará em nosso componente App, no qual podemos definir um cabeçalho e um rodapé para o app, bem como um componente VideoChat. Dentro do componente VideoChat, queremos mostrar um componente Lobby no qual o usuário pode inserir seu nome e a sala em que deseja entrar. Depois que eles inserirem esses detalhes, substituiremos Lobby por um componente Room que lidará com a conexão com a sala e exibirá os participantes no chat por vídeo. Por fim, para cada participante na sala, renderizaremos um componente Participant que lidará com a exibição de sua mídia.

Construir os componentes

O componente do app

Abra src/App.js. Há muito código aqui do aplicativo de exemplo inicial que podemos remover. Além disso, o App é um componente baseado em classe. Dissemos que criaríamos todo o app com componentes funcionais, então é melhor mudarmos isso.

Nas importações, remova Component e a importação do logo.svg. Substitua toda a classe App por uma função que renderiza nosso esqueleto de aplicativo. O arquivo inteiro deve ficar parecido com isto:

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;

O componente VideoChat

Este componente mostrará um lobby ou uma sala com base no fato de o usuário ter inserido um nome de usuário e um nome de sala. Crie um novo arquivo de componente src/VideoChat.js e inicie-o com o seguinte padrão:

import React from 'react';

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

export default VideoChat;

O componente VideoChat será o componente de nível superior para lidar com os dados sobre o chat. Precisaremos armazenar um nome de usuário para o usuário que está ingressando no chat, um nome de sala para a sala à qual ele se conectará e o access token (token de acesso) depois que ele for obtido do servidor. Criaremos um formulário para inserir alguns desses dados no próximo componente.

Com React Hooks, usamos o useState hook para armazenar esses dados.

useState

useState é uma função que usa um único argumento, que é o estado inicial e retorna uma matriz contendo o estado atual e uma função para atualizar esse estado. Vamos desestruturar esse array para nos dar duas variáveis distintas, como state e setState. Usaremos setState para rastrear o nome de usuário, o nome da sala e o token dentro de nosso componente.

Comece importando useState do React e configure os estados para o nome de usuário, nome da sala e token:

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
};

Em seguida, precisamos de duas funções para lidar com a atualização username e roomName quando o usuário os inserir em seus respectivos elementos de entrada.

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
};

Embora isso funcione, podemos otimizar nosso componente usando outro React hook aqui; useCallback

useCallback

Sempre que esse componente de função é chamado, as funções handleXXX são redefinidas. Eles precisam fazer parte do componente porque dependem das funções setUsername e setRoomName, mas sempre serão iguais. useCallback é um React hook que nos permite armazenar as funções. Ou seja, se eles forem os mesmos entre as chamadas de função, não serão redefinidos.

useCallback usa dois argumentos, a função a ser armazenada e uma matriz das dependências da função. Se qualquer uma das dependências da função mudar, isso implica que a função armazenada está desatualizada e a função é redefinida e armazenada novamente.

Nesse caso, não há dependências para essas duas funções, portanto uma matriz vazia será suficiente (as funções setState do hook useState são consideradas constantes dentro da função). Reescrevendo essa função, precisamos adicionar useCallback à importação na parte superior do arquivo e, em seguida, finalizar cada uma dessas funções.

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
};

Quando o usuário envia o formulário, queremos enviar o nome de usuário e o nome da sala ao servidor para trocar por um token de acesso que podemos usar para entrar na sala. Vamos criar essa função também neste componente.

Usaremos a API de busca para enviar os dados como JSON para o endpoint, receber e analisar a resposta e, em seguida, usar setToken para armazenar o token em nosso estado. Também encerraremos essa função com useCallback, mas, nesse caso, a função dependerá do username e roomName, portanto, adicionamos essas funções como as dependências de useCallback.

  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
};

Para a função final neste componente, vamos adicionar uma funcionalidade de logout. Isso ejetará o usuário de uma sala e o levará de volta ao lobby. Para isso, vamos definir o token como null. Mais uma vez, encerramos isso em useCallback sem dependências.

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

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

Esse componente está orquestrando principalmente os componentes abaixo dele e portanto, não há muito a ser renderizado até que tenhamos criado esses componentes. Vamos criar o componente Lobby que renderiza o formulário que solicita um nome de usuário e um nome de sala em seguida.

O componente Lobby

Crie um novo arquivo em src/Lobby.js. Esse componente não precisa armazenar dados, pois ele passará todos os eventos até seu componente principal, o VideoChat. Quando o componente for renderizado, ele passará o username e roomName, bem como as funções para lidar com as alterações em cada um e lidar com o envio do formulário. Podemos desestruturar esses adereços para facilitar o uso deles posteriormente.

O trabalho principal do componente Lobby é renderizar o formulário usando esses adereços, como este:

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;

Vamos atualizar o componente VideoChat para renderizar Lobby a menos que tenhamos um token, caso contrário, renderizaremos o usernameroomName e token. Precisaremos importar o componente Lobby na parte superior do arquivo e renderizar alguns JSX na parte inferior da função do componente:

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;
};

Para que isso seja exibido na página, também precisamos importar o componente VideoChat para o componente App e renderizá-lo. Abra src/App.js novamente e faça as seguintes alterações:

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;

Verifique se o aplicativo ainda está em execução, ou reinicie-o com npm run dev e abra-o no navegador para ver o formulário. Preencha um nome de usuário e o nome da sala e envie, e a visualização será alterada para mostrar os nomes que você escolheu, além do token recuperado do servidor.

Preencha e envie o formulário. Você verá o nome de usuário, o nome da sala e o token na página.

O componente Room

Agora que adicionamos um nome de usuário e um nome de sala ao aplicativo, podemos usá-los para participar de uma sala de chat pelo Twilio Video. Para trabalhar com o serviço do Twilio Video, precisaremos do SDK JS. Vamos trabalhar com a versão 2.2.0 da Twilio Video para este exemplo, instale-o com:

npm install twilio-video@^2.2.0 --save

Crie um novo arquivo no diretório src chamado Room.js. Comece-o com a seguinte padrão. Usaremos o SDK do Twilio Video neste componente, bem como os hooks useState e useEffect. Também vamos obter roomNametoken e handleLogout como adereços do componente principal VideoChat:

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

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

};

export default Room;

A primeira coisa que o componente fará é conectar-se ao serviço do Twilio Video usando o token e roomName (nome da sala). Quando nos conectamos, obtemos um objeto room, que desejaremos armazenar. A sala também inclui uma lista de participantes que mudarão com o tempo, portanto, também os armazenaremos. Usaremos useState para armazená-los, os valores iniciais serão null para a sala e uma matriz vazia para os participantes:

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

Antes de entrar na sala, vamos renderizar algo para esse componente. Vamos mapear a matriz de participantes para mostrar a identidade de cada participante e também mostrar a identidade do participante local na sala:

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>
  );
});

Vamos atualizar o componente VideoChat para renderizar esse componente Room no lugar das informações de espaço reservado que tínhamos anteriormente.

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;
};

Executar isso no navegador mostrará o nome da sala e o botão de logout, mas nenhuma identidade de participante, pois ainda não conectamos e ingressamos na sala.

Ao enviar o formulário, você verá o nome da sala, &#x27;Awesome Room&#x27; (Sala incrível) neste caso e um espaço para os participantes remotos.

Temos todas as informações de que precisamos para entrar em uma sala, portanto, devemos executar a ação para conectar na primeira renderização do componente. Também queremos sair da sala depois que o componente for destruído (nenhum ponto mantendo uma conexão WebRTC em segundo plano). Esses são os efeitos colaterais.

Com componentes baseados em classe, é onde você usaria os métodos de ciclo de vida componentDidMount e componentWillUnmount. Com os React hooks, usaremos useEffect hook.

useEffect

useEffect é uma função que usa um método e o executa quando o componente é renderizado. Quando nossos componentes são carregados, queremos nos conectar ao serviço de vídeo, também precisamos de funções que possamos executar sempre que um participante entrar ou sair da sala para adicionar e remover participantes do estado, respectivamente.

Vamos começar a criar nosso hook adicionando este código antes do JSX em Room.js:

  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);
    });
  });

Ele usa token e roomName para se conectar ao serviço do Twilio Video. Quando a conexão estiver concluída, definimos o estado da sala, criamos um ouvinte para outros participantes conectando ou desconectando e fazendo loop por meio de todos os participantes existentes adicionando-os ao estado da matriz participantes usando a função participantConnected que escrevemos anteriormente.

Este é um bom começo, mas se removemos o componente, ainda estaremos conectados à sala. Por isso, também precisamos também nos remover.

Se retornarmos uma função de retorno de chamada, passaremos para useEffect, ela será executada quando o componente for desmontado. Quando um componente que usa useEffect é renderizado novamente, essa função também é chamada para limpar o efeito antes de ser executada novamente.

Vamos retornar uma função que interrompe todas os rastros do participante local e, em seguida, desconecta da sala, se o participante local estiver conectado:

    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;
        }
      });
    };
  });

Observe que aqui usamos a versão de retorno de chamada da função setRoom que recebemos do useState anteriormente. Se você passar uma função para setRoom, ela será chamada com o valor anterior, nesse caso, a sala existente que chamaremos currentRoom e definirá o estado para o que você retornar.

Mas ainda não terminamos. Em seu estado atual, esse componente sairá de uma sala acompanhada e se reconectará a ela toda vez que for renderizado novamente. Isso não é o ideal, então precisamos dizer quando ele deve limpar e executar o efeito novamente. Muito parecido com useCallback, fazemos isso passando uma matriz de variáveis das quais o efeito depende. Se as variáveis tiverem sido alteradas, primeiro queremos limpar e, em seguida, executar o efeito novamente. Se eles não mudaram, não há necessidade de executar o efeito novamente.

Olhando para a função, podemos ver que se roomName ou token fossem alterados, esperamos nos conectar a uma sala diferente ou como um usuário diferente. Vamos passar essas variáveis como um array para useEffect também:

    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]);

Observe que temos duas funções de retorno de chamada definidas neste efeito. Você pode pensar que eles devem ser envolvidos em useCallback como fizemos anteriormente, mas esse não é o caso. Como eles fazem parte do efeito, eles serão executados somente quando as dependências forem atualizadas. Você também não pode usar hooks dentro das funções de retorno de chamada, eles devem ser usados diretamente nos componentes ou em um hook personalizado.

Terminamos com este componente. Vamos verificar se ele está funcionando até agora, recarregue o aplicativo e insira um nome de usuário e um nome de sala. Você deve ver sua identidade aparecer quando entrar na sala. Clicar no botão de logout levará você de volta ao lobby.

Ao enviar o formulário, você verá tudo o que você viu antes e a sua própria identidade.

A parte final do quebra-cabeça é renderizar os participantes na chamada de vídeo, adicionando seu vídeo e áudio à página.

O componente Participant

Crie um novo componente no src chamado Participant.js. Vamos começar com o padrão, embora neste componente vamos usar três hooks, useState e useEffect, que vimos, e useRef. Também passaremos um objeto participant nos adereços e acompanharemos os vídeos e as faixas de áudios do participante com useState:

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

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

export default Participant;

Quando obtivermos uma transmissão de vídeo ou áudio de nosso participante, vamos anexá-la a um elemento <video> ou <audio>. Como o JSX é declarativo, não temos acesso direto ao DOM (Document Object Model), então precisamos obter uma referência ao elemento HTML de outra maneira.

O React fornece acesso ao DOM por meio de refs e useRef hook. Para usar referências, nós as declaramos antecipadamente e fizemos referência dentro do JSX. Criamos nossas referências usando o hook useRef antes de processarmos qualquer coisa:

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

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

Por enquanto, vamos retornar o JSX que queremos. Para conectar o elemento JSX à ref, usamos o atributo ref.

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>
  );
 });

Eu também defini os atributos e as tags <video> e <audio> para reprodução automática, para que elas sejam reproduzidas assim que tiverem uma transmissão de mídia, e mudo, para eu não ensurdecer com feedback durante o teste, você me agradecerá por isso se cometer esse erro

Este componente não faz muito ainda, pois precisamos usar alguns efeitos. Na verdade, usaremos o hook useEffect três vezes neste componente, você verá por que em breve.

O primeiro hook useEffect definirá as faixas de vídeo e áudio no estado e definirá ouvintes para o objeto participante para quando as faixas forem adicionadas ou removidas. Ele também precisará limpar e remover esses ouvintes e esvaziar o estado quando o componente for desmontado.

Em nosso primeiro hook useEffect, adicionaremos duas funções que serão executadas quando uma faixa for adicionada ou removida do participante. Estas funções verificam se a faixa é uma faixa de áudio ou vídeo e, em seguida, a adicionam ou removem do estado utilizando a função de estado relevante.

  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

Em seguida, usamos o objeto participante para definir os valores iniciais para as faixas de áudio e vídeo. Os participantes têm propriedades de videoTracks e audioTracks que retornam um mapa de TrackPublication objetos. Uma TrackPublication não tem acesso a seu objeto track até que ele seja assinado, então precisamos filtrar todas as faixas que não existem. Faremos isso com uma função que mapeia de TrackPublication para Track e filtra qualquer uma que seja null.

Em seguida, configuramos ouvintes para os eventos trackSubscribed e trackUnsubscribed usando as funções que acabamos de escrever e, em seguida, fazemos a limpeza na função retornada:

  const trackpubsToTracks = trackMap => Array.from(trackMap.values())
    .map(publication => publication.track)
    .filter(track => track !== null);

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

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

    setVideoTracks(trackpubsToTracks(participant.videoTracks));
    setAudioTracks(trackpubsToTracks(participant.audioTracks));

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

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

  return (
    <div className="participant">

Observe que o hook depende apenas do objeto participant e não será limpo e executado novamente, a menos que o participante mude.

Também precisamos do hook useEffect para conectar as faixas de vídeo e áudio ao DOM, vou mostrar apenas uma delas aqui, a versão de vídeo, mas o áudio é o mesmo se você substituir o vídeo por áudio. O hook obterá a primeira faixa de vídeo do estado e, se existir, vai anexá-la ao nó DOM que capturamos com uma referência anterior. Você pode se referir ao nó DOM atual na ref usando videoRef.current. Se anexarmos a faixa de vídeo, também precisaremos retornar uma função para destacá-la durante a limpeza.

  }, [participant]);

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

  return (
    <div className="participant">

Repita esse hook para audioTracks e estamos prontos para processar nosso componente Participant do componente Room. Importe o componente Participant na parte superior do arquivo e substitua os parágrafos que exibiram a identidade pelo próprio componente.

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>
  );
});

Agora recarregue o aplicativo, entre em uma sala e você verá a si mesmo na tela. Abra outro navegador e entre na mesma sala e você se verá duas vezes. Pressione o botão de logout e você estará de volta no lobby.

Sucesso! Agora você deve conseguir se ver em um chat por vídeo com você mesmo.

Conclusão

Construir com o Twilio Video no React exige um pouco mais de trabalho porque há todos os tipos de efeitos colaterais para lidar. Desde fazer uma solicitação para obter o token, conectar-se ao serviço de vídeo e manipular o DOM para conectar elementos de <video> e <audio>, há muitas coisas para se familiarizar. Nesta publicação, vimos como usar useStateuseCallbackuseEffect e useRef para controlar esses efeitos colaterais e criar nosso aplicativo usando apenas componentes funcionais.

Esperamos que isso ajude você a entender o Twilio Video e React Hooks. Todo o código-fonte deste aplicativo está disponível no GitHub para que você possa quebrar e montar novamente.

Para ler mais sobre React Hooks, dê uma olhada na documentação oficial, que é muito completa, nesta visualização sobre pensar em hooks e confira o mergulho profundo de Dan Abramov no useEffect (é um post longo, mas vale a pena, eu prometo).

Se quiser saber mais sobre como criar vídeos com o Twilio Video, confira essas publicações sobre como trocar câmeras durante um chat por vídeo ou adicionar compartilhamento de tela ao chat por vídeo.

Este artigo foi traduzido do original "Build a Video Chat with React Hooks". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.