Crear un chat de video con Hooks de React

October 09, 2019
Redactado por
Phil Nash
Twilion

Crear un chat de video con Hooks de React

Hemos visto un chat de video integrado en React este blog antes, pero desde entonces, en la versión 16.8, React lanzó Hooks. Hooks le permite usar características de estado u otras características de React dentro de los componentes funcionales en lugar de escribir un componente de clase.

En esta publicación, vamos a crear una aplicación de chat de video mediante Twilio Video y React solo con los componentes funcionales, mediante el uso de los hooks useStateuseCallbackuseEffect y useRef.

Lo que necesitará

Para crear esta aplicación de chat de video, necesitará lo siguiente:

Una vez que tenga todo eso, podemos preparar nuestro entorno de desarrollo.

Cómo empezar

Para que podamos ir directamente a la aplicación React, podemos comenzar con la app de inicio React y Express que creé. Descargue o copie la rama “twilio” de la app de inicio, cambie al nuevo directorio e instale las dependencias:

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

Copie el archivo .env.example en .env.

cp .env.example .env

Ejecute la aplicación para asegurarse de que todo funciona como se espera:

npm run dev

Debe ver esta página cargada en el navegador:

La página inicial muestra el logotipo React y un formulario

Preparar las credenciales de Twilio

Para conectarnos a Twilio Video, necesitaremos algunas credenciales. Desde su consola de Twilio, copie el SID de su cuenta e ingréselo en el archivo .env como el TWILIO_ACCOUNT_SID

También necesitará una clave y un secreto de API. Puede crearlas en las Herramientas de video programables de su consola. Cree un par de claves y agregue el SID y el secreto como TWILIO_API_KEY y TWILIO_API_SECRET al archivo .env.

Agregar un poco de estilo

No vamos a preocuparnos por las habilidades de las hojas de estilo en cascada (CSS) en esta publicación, pero vamos a agregar algunas para que el resultado no se vea horrible. Obtenga la CSS desde esta dirección URL y reemplace el contenido de src/App.css con eso.

Ahora estamos listos para comenzar a crear.

Planificar nuestros componentes

Todo comenzará en nuestro componente App, donde podremos colocar un encabezado y pie de página para la app, así como un componente VideoChat. Dentro del componente VideoChat, queremos mostrar un componente Lobby, donde el usuario pueda ingresar su nombre y la sala a la que desea unirse. Una vez que hayan ingresado esos detalles, reemplazaremos el Lobby por un componente Room que se encargará de conectarse a la sala y de mostrar a los participantes en el chat de video. Por último, para cada participante de la sala, representaremos un componente Participant, que se encargará de mostrar sus medios.

Crear los componentes

El componente de la app

Abra src/App.js, aquí econtrará muchos códigos de la app de ejemplo inicial que podemos eliminar. Además, el componente App es un componente basado en la clase. Dijimos que desarrollaremos la app completa con componentes funcionales, de modo que tenemos que cambiar eso.

De las importaciones, elimine Component y la importación del logo.svg. Reemplace toda la clase App por una función que represente nuestro esqueleto de aplicación. Todo el archivo debe verse de la siguiente manera:

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;

El componente Videochat

Este componente mostrará un lobby o una sala según si el usuario ingresó un nombre de usuario y un nombre de sala. Cree un nuevo archivo de componente src/VideoChat.js e inícielo con el siguiente texto modelo:

import React from 'react';

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

export default VideoChat;

El componente VideoChat será el componente de nivel superior para manejar los datos del chat. Necesitaremos almacenar un nombre de usuario para el usuario que se une al chat, un nombre de sala a la que se conectará y su token de acceso, una vez que se haya obtenido del servidor. Vamos a crear un formulario para ingresar algunos de estos datos en el siguiente componente.

Con Hooks de React, utilizamos el useState hook para almacenar estos datos.

useState

useState es una función que toma un solo argumento, el estado inicial, y devuelve una matriz que contiene el estado actual y una función para actualizar ese estado. Vamos a desestructurar esa matriz para que nos dé dos variables diferentes como state y setState. Utilizaremos setState para rastrear el nombre de usuario, el nombre de la sala y el token dentro de nuestro componente.

Comience importando useState de React y configure los estados según el nombre de usuario, el nombre de la sala y el 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
};

A continuación, necesitamos dos funciones para manejar la actualización de username y roomName cuando el usuario los ingresa en sus 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
};

Si bien esto funcionará, podemos optimizar nuestro componente utilizando otro hook de React aquí: useCallback.

useCallback

Cada vez que se llama este componente de función, se redefinen las funciones handleXXX. Deben ser parte del componente porque dependen de las funciones setUsername y setRoomName, pero siempre serán las mismas. useCallback es un hook de React que nos permite  las funciones memoizar. Es decir, si son iguales entre las invocaciones de funciones, no se redefinirán.

useCallback toma dos argumentos: la función que será memoizada y una matriz de las dependencias de la función. Si cambia alguna de las dependencias de la función, significa que la función memoizada está desactualizada y que, luego, se vuelve a definir y a memoizar la función.

En este caso, no existen dependencias para estas dos funciones, por lo que será suficiente una matriz vacía (se considera que las funciones setState del hook useState son constantes dentro de la función). Cuando volvemos a escribir esta función debemos agregar useCallback a la importación en la parte superior del archivo y, luego, terminar cada una de estas funciones.

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

Cuando el usuario envía el formulario, queremos enviar el nombre de usuario y el nombre de la sala al servidor para que se intercambien por un token de acceso que podamos utilizar para ingresar a la sala. También crearemos esa función en este componente.

Utilizaremos la API Fetch para enviar los datos como JSON al punto final, recibir y analizar la respuesta, y, luego, utilizaremos setToken para almacenar el token en nuestro estado. También vamos a terminamos esta función con useCallback, pero, en este caso, la función dependerá del username y del roomName, de modo que los agregamos como las dependencias para 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 la función final en este componente, agregaremos una funcionalidad de cierre de sesión. Esto expulsará al usuario de una sala y lo devolverá al lobby. Para hacerlo, estableceremos el token en null. Una vez más, terminamos esto en useCallback sin dependencias.

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

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

Este componente está organizando, principalmente, los componentes que están por debajo de él, por lo que no hay mucho que mostrar hasta que creemos esos componentes. Vamos a crear el componente Lobby que representa el formulario que solicita un nombre de usuario y el nombre de la sala a continuación.

El componente Lobby

Cree un nuevo archivo en src/Lobby.js. Este componente no necesita almacenar datos, ya que pasará todos los eventos a su elemento principal, el componente de Videochat. Cuando se presenta el componente, se pasa el username y el roomName, así como las funciones para gestionar los cambios de cada uno y gestionar el envío del formulario. Podemos desestructurar los accesorios para que sea más fácil utilizarlos en el futuro.

La tarea principal del componente Lobby es realizar el formulario utilizando los accesorios, de la siguiente manera:

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;

Actualicemos el componente VideoChat para representar el Lobby, a menos que tengamos un token, de lo contrario, se presentarán el username, roomName y token. Tendremos que importar el componente Lobby en la parte superior del archivo y presentar algunos JSX en la parte inferior de la función del 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 esto aparezca en la página, también debemos importar el componente VideoChat al componente App y mostrarlo. Abra de nuevo src/App.js y realice los siguientes cambios:

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;

Asegúrese de que la aplicación todavía esté en ejecución (o reiníciela con npm run dev), luego, ábrala en el navegador y verá un formulario. Ingrese un nombre de usuario y un nombre de sala, y haga clic en Submit (Enviar), y la vista cambiará para mostrarle los nombres que eligió más el token recuperado del servidor.

Complete el formulario y envíelo. A continuación, verá el nombre de usuario, el nombre de la sala y el token en la página.

El componente Room

Ahora que hemos agregado un nombre de usuario y un nombre de sala a la aplicación, podemos usarlos para unirnos a una sala de chat de Twilio Video. Para trabajar con el servicio de Twilio Video, necesitaremos el SDK de JS. Trabajaremos con Twilio Video versión 2.2.0 para este ejemplo, instálelo con lo siguiente:

npm install twilio-video@^2.2.0 --save

Cree un nuevo archivo en el directorio src llamado Room.js. Empiécelo con el siguiente texto modelo. Usaremos el SDK de Twilio Video en este componente, así como los hooks useState y useEffect. También obtendremos roomName, token y handleLogout como accesorios del componente principal VideoChat:

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

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

};

export default Room;

Lo primero que hará el componente es conectarse al servicio de Twilio Video mediante el token y el nombre de la sala. Cuando nos conectamos, obtendremos un objeto room, que deseamos almacenar. La sala también incluye una lista de participantes que cambiará con el tiempo, por lo que también los almacenaremos. Utilizaremos useState para almacenarlos. Los valores iniciales serán null para la sala y una matriz vacía para los participantes:

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

Antes de que podamos unirnos a la sala, presentemos algo para este componente. Asignaremos valores a la matriz de participantes para mostrar la identidad de cada participante y también mostrar la identidad del participante local en la 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>
  );
});

Actualicemos el componente de VideoChat para presentar este componente Room en lugar de la información del marcador de posición que teníamos 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;
};

Si se ejecuta esto en el navegador, se mostrará el nombre de la sala y el botón de cierre de sesión, pero no la identidad de los participantes ya que aún no nos hemos conectado y unido a la sala.

Ahora, cuando envíe el formulario, verá el nombre de la sala, &#x27;Awesome Room&#x27; (&#x27;Sala fantástica&#x27;), en este caso, y un espacio para los participantes remotos.

Tenemos toda la información que necesitamos para unirnos a una sala, por lo que debemos activar la acción para conectarnos en la primera presentación del componente. También queremos salir de la sala una vez que el componente se destruye (no tiene sentido mantener una conexión WebRTC en segundo plano). Estos son efectos secundarios.

Con los componentes basados en la clase, aquí es donde se utilizaría los métodos de ciclo de vida de componentDidMount y componentWillUnmount. Con React Hooks, usaremos el useEffecthook.

useEffect

useEffect es una función que toma un método y lo ejecuta una vez que el componente se ha representado. Cuando se carga nuestro componente, queremos conectarnos con el servicio de video. También necesitaremos funciones que podamos ejecutar cada vez que un participante se una o se retire de la sala para agregar y quitar participantes del estado, respectivamente.

Vamos a empezar a crear nuestro hook al agregar este código antes de JSX en 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);
    });
  });

Esto utiliza el token y roomName para conectarse al servicio de Twilio Video. Una vez finalizada la conexión, vamos a establecer un estado de la sala, configurar una audiencia para los demás participantes que se conecten y desconecten, y realizar un ciclo a través de cualquier participante existente que los agregue al estado de la matriz de los participantes mediante la función participantConnected que escribimos anteriormente.

Este es un buen comienzo, pero si quitamos el componente, seguiremos conectados a la sala. Por lo que también tenemos que ordenar lo que usamos.

Si devolvemos una función desde la devolución de llamada pasamos a useEffect, que se ejecutará cuando el componente esté desarmado. Cuando un componente que utiliza useEffect se vuelve a presentar, a esta función también se la llama para limpiar el efecto antes de que se ejecute de nuevo.

Vamos a devolver una función que detiene todas las pistas de los participantes locales y, luego, se desconecta de la sala, en caso de que el participante local esté 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;
        }
      });
    };
  });

Tenga en cuenta que aquí utilizamos la versión de devolución de llamada de la función setRoom que obtuvimos de useState anteriormente. Si pasa una función a setRoom, entonces se llamará con el valor anterior, en este caso la sala existente que llamaremos currentRoom, y establecerá el estado en lo que sea que devuelva.

Sin embargo, aún no hemos terminado. En su estado actual, este componente saldrá de una sala, y se volverá a conectar a ella cada vez que se vuelva a presentar. Esto no es lo ideal, por lo que debemos decirle cuándo se debe ordenar y volver a ejecutar el efecto. Al igual que con useCallback, lo hacemos pasando una matriz de variables de las que depende el efecto. Si las variables han cambiado, primero, queremos ordenar y, a continuación, volver a ejecutar el efecto. Si no han cambiado, no es necesario volver a ejecutar el efecto.

Si observamos la función, podemos ver que si roomName o token cambia, debemos esperar conectarnos a otra sala o como un usuario diferente. Pasemos esas variables como una matriz para useEffect también:

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

Tenga en cuenta que tenemos dos funciones de devolución de llamada definidas en este efecto. Es posible que crea que se deben terminar en useCallback, como lo hicimos anteriormente, pero no es así. Debido a que son parte del efecto, solo se ejecutarán cuando se actualicen las dependencias. Tampoco puede utilizar hooks dentro de las funciones de devolución de llamada, se deben utilizar directamente dentro de los componentes o en un hook personalizado.

En general, hemos terminado con este componente. Verifiquemos que funciona hasta ahora. Vuelva a cargar la aplicación e ingrese un nombre de usuario y un nombre de sala. Debe ver que aparece su identidad cuando se une a la sala. Si hace clic en el botón de cierre de sesión, volverá al lobby.

Ahora, cuando envíe el formulario, verá todo antes, además de su propia identidad.

La última pieza del rompecabezas es representar a los participantes en la videollamada y agregar el video y el audio a la página.

El componente Participant

Cree un nuevo componente en src llamado Participant.js. Comenzaremos con el texto modelo habitual, aunque en este componente vamos a utilizar tres hooks, useState y useEffect, que ya hemos visto, y useRef. También pasaremos un objeto participant en los accesorios y haremos un seguimiento del video y de las pistas de audio del participante con useState:

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

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

export default Participant;

Cuando obtenemos una transmisión de video o audio de nuestro participante, desearemos adjuntarla a un elemento <video> o <audio> Como JSX es declarativo, no obtenemos acceso directo al modelo de objetos del documento (DOM), por lo que necesitamos hacer una referencia al elemento HTML de otra manera.

React brinda acceso al DOM a través de referencias y del useRefhook. Para usar referencias las declaramos al principio y, luego, las mencionamos dentro de JSX. Creamos nuestras referencias con el hook usRef, antes de presentar cualquier cosa:

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

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

Por ahora, devolvamos el JSX que queremos. Para conectar el elemento JSX a la referencia, utilizamos el 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>
  );
 });

También he configurado los atributos de las etiquetas <video> y <audio> para que se reproduzcan de forma automática (de modo que se reproduzcan tan pronto como tengan una transmisión de medios) y se silencien (de modo que no me quede sordo con los comentarios durante las pruebas, me lo agradecerán si alguna vez cometen este error).

Este componente aún no hace mucho, ya que necesitamos utilizar algunos efectos. En realidad, usaremos el hook useEffect tres veces en este componente, pronto verá por qué.

El primer hook useEffect útil establecerá las pistas de video y audio en el estado y establecerá la audiencia en el objeto de participante cuando se agreguen o eliminen las pistas. También deberá ordenar y eliminar a esa audiencia y vaciar el estado cuando el componente se desmonte.

En nuestro primer hook useEffect, agregaremos dos funciones que se ejecutarán cuando se agregue o elimine una pista del participante. Estas funciones permiten comprobar si la pista es de audio o video y, a continuación, agregarla o quitarla del estado mediante la función de estado correspondiente.

  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

A continuación, utilizamos el objeto de participante para establecer los valores iniciales para las pistas de audio y video. Los participantes tienen propiedades de videoTracks y audioTracks que devuelven un Mapa de objetos TrackPublication. Una TrackPublication no tiene acceso a su objeto track hasta que se suscribe, por lo que debemos filtrar las pistas que no existan. Esto lo haremos con una función que se asigna de TrackPublication a Tracks y filtra cualquiera que es null.

Luego, configuramos la audiencia en los eventos trackSubscribed y trackUnsubscribed con las funciones que acabamos de escribir y, a continuación, hacemos la limpieza en la función de vuelta:

  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">

Tenga en cuenta que el hook solo depende del objeto participant y no se ordenará ni se volverá a ejecutar a menos que cambie el participante.

También necesitamos un hook useEffect para adjuntar las pistas de video y audio al DOM. Mostraré solo una de ellas aquí, la versión de video, pero el audio es lo mismo si reemplaza el video por audio. Elhook obtendrá la primera pista de video del estado y, si existe, se adjuntará al nodo del DOM que registramos con una referencia anterior. Puede consultar el nodo del DOM actual en la referencia mediante videoRef.current. Si adjuntamos la pista de video, también tendremos que volver a una función para quitarla cuando se ordene.

  }, [participant]);

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

  return (
    <div className="participant">

Repita ese hook para videoRef.current, y estamos listos para representar nuestro componente Participant desde el componente Room. Importe el componente Participant en la parte superior del archivo y, a continuación, reemplace los párrafos que muestran la identidad con el componente mismo.

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

Ahora vuelva a cargar la aplicación, únase a una sala y se verá en la pantalla. Abra otro navegador y únase a la misma sala, y se verá dos veces. Presione el botón Logout (Cerrar sesión) y volverá al lobby.

¡Lo hizo correctamente! Ahora debería verse en un chat de video con usted mismo.

Conclusión

Cuando se crea con Twilio Video en React, se requiere un poco más de trabajo, ya que existen muchos tipos de efectos secundarios con los que se debe lidiar. Desde hacer una solicitud para obtener el token, conectarse al servicio de Video hasta manipular el DOM para conectar los elementos <video> y <audio>, hay bastantes aspectos de los que ocuparse. En esta publicación hemos visto cómo usar useState, useCallback, useEffect y useRef para controlar estos efectos secundarios y construir nuestra aplicación solo con componentes funcionales.

Esperamos que esto lo ayude a comprender tanto Twilio Video como React Hooks. Todos los códigos fuente de esta aplicación están disponibles en GitHub para que los separe y vuelva a armar.

Para leer más sobre React Hooks, consulte la documentación oficial, que es muy detallada, esta visualización de pensar en hooks y también eche un vistazo a la investigación detallada de Dan Abrams sobre useEffect (es una publicación larga, pero vale la pena, lo prometo).

Si desea obtener más información acerca de cómo crear con Twilio Video, consulte estas publicaciones sobre el cambio de cámaras durante un chat de video o sobre cómo agregar la opción para compartir pantalla a su chat de video.

Este artículo fue traducido del original "Build a Video Chat with React Hooks". Mientras estamos en nuestros procesos de traducción, nos encantaría recibir sus comentarios en help@twilio.com - las contribuciones valiosas pueden generar regalos de Twilio.