Construire un Chat Vidéo avec React Hooks
Temps de lecture: 12 minutes
On avait déjà vu la construction d’un chat vidéo avec React dans un article précédent, mais depuis sa publication, React a sorti la version 16.8 de Hooks. Hooks nous laisse utiliser State ou n’importe quelle autre fonction React dans des composants fonctionnels au lieu de devoir coder une classe composant.
Dans ce post, nous allons construire une application vidéo chat en utilisant Twilio Video et React avec des composants fonctionnels uniquement, en utilisant les hooks useState
, useCallback
, useEffect
et useRef
.
Ce dont vous aurez besoin
Pour construire cette application de chat vidéo, vous aurez besoin :
- D’installer Node.js and npm
- D’avoir un compte Twilio (inscrivez-vous gratuitement sur Twilio ici)
Une fois que vous avez tout ça, nous pouvons préparer notre environnement de développement.
Pour commencer
Pour pouvoir passer directement à l’application React, vous pouvez vous lancer avec l’application que j’ai créée, React and Express starter. Téléchargez ou clonez la branche twilio de l’app starter, allez dans le nouveau répertoire et installez les dépendances :
Copiez le fichier .env.example
sur .env
.
Exécutez l’application pour vous assurer que tout fonctionne comme prévu :
Vous devriez voir cette page dans le browser :
Préparer les identifiants Twilio
Pour vous connecter à Twilio Video, vous aurez besoin de quelques identifiants. Depuis la console Twilio, copiez votre Account SID (SID de compte) et collez-le dans le fichier .env
comme suit : TWILIO_ACCOUNT_SID
.
Nous aurons aussi besoin d’une clé et d’un secret API - que l’on peut créer sur notre console dans les Programmable Video Tools. Il faut créer la paire de clé et ajouter le SID et Secret dans le fichier .env
à la place de TWILIO_API_KEY
et TWILIO_API_SECRET
.
Ajouter un peu de style
Nous n’allons pas nous embêter plus que nécessaire avec CSS dans ce tutoriel, mais ajoutons-en tout de même un peu afin que le résultat ne soit pas trop moche ! Prenons donc le CSS via cette URL et remplaçons le contenu de src/App.css
par celle-ci.
Nous sommes maintenant prêts à commencer !
Prévoir nos composants
Tout commencera d’abord dans notre composant App
où l’on peut poser un header (en-tête) et un footer (pied-de-page) pour l’application, ainsi qu’un composant VideoChat
. A l’intérieur du composant VideoChat
, on souhaite montrer un composant Lobby
où l’utilisateur peut entrer son prénom et le salon qu’ils veulent rejoindre. Une fois que c’est fait, on remplace le composant Lobby
par Room
, qui s’occupera de connecter le salon et d’afficher les participants dans la discussion video.
Et enfin, pour chaque participant dans le salon, nous rendrons un composant Participant
qui s’occupera d’afficher leur média.
Coder les composants
Le composant App
En ouvrant src/App.js
, on peut voir qu’il y a beaucoup de code - issu de l’exemple d’app initial - que l’on peut retirer. A savoir aussi, le composant App
est un composant de classe : comme nous avons dit que l’on construirait l’application entière avec des composants fonctionnels, on ferait mieux de changer ça !
Dans les imports, enlevez Component
et l’import du logo.svg. Remplaçons la classe App entière par une fonction qui renvoie la structure de de notre application. Le fichier complet devrait ressembler à ça :
Le composant VideoChat
Ce composant va afficher un salon basé sur le nom d’utilisateur ou sur le nom du salon que l’utilisateur a entré. Créons un nouveau fichier composant src/VideoChat.js
et lançons le avec le boilerplate suivant :
Le composant VideoChat
sera le meilleur composant pour gérer les données du chat. Nous aurons besoin d’enregistrer un username pour l’utilisateur qui rejoint la discussion, un nom pour le salon auquel ils vont se connecter et leur access token, une fois qu’il a été récupéré du serveur. On va développer un formulaire pour entrer quelques unes de ces données dans le composant suivant.
Avec React Hooks, on utilise le hook useState pour stocker ces données.
useState
La fonction useState
prend un seul argument , l’état initial, et affiche un tableau qui contient l’état actuel ainsi qu’une autre fonction pour mettre à jour cet état. Nous allons déstructurer ce tableau pour qu’il nous rende deux variables distinctes comme state
et setState
. On utilisera setState
pour tracker le nom d’utilisateur, le nom du salon, et le token à l’intérieur de notre composant.
Commençons par importer useState
à partir de React et configurons les états du nom d’utilisateur, du nom de salon et du token:
Next we need two functions to handle updating the username and roomName when the user enters them in their respective input elements.
Ensuite, nous avons besoin de deux fonctions pour gérer la mise à jour de username
et de roomName
une fois que l’utilisateur les entrera dans leur éléments de saisie respectifs.
Bien que ça fonctionne, on peut tout de même optimiser notre composant en utilisant un autre hook React ; useCallback
.
useCallback
A chaque fois qu’on appelle une fonction composant, les fonctions handleXXX
sont redéfinies. Elles ont besoin de faire partie du composant parce qu’elles s’appuient sur les fonctions setUsername
et setRoomName
, mais elles seront pareilles à chaque fois. Le hook React useCallback
nous permet de mémoïser les fonctions. C’est-à-dire que si elles sont les mêmes entre les invocations de fonctions, elles ne seront pas redéfinies.
useCallback
prend deux arguments, la fonction qui doit être mémoïsée et un tableau des dépendances des fonctions. Si l’une de ces dépendances change, ça implique que la fonction mémoïsée est dépassée. La fonction est alors redéfinie et mémoïsée à nouveau.
Dans ce cas, les deux fonctions n’ont pas de dépendances donc un tableau vide sera suffisant (les fonctions setState
du hook useState
sont réputées pour être constantes au sein de la fonction). Pour recoder cette fonction, on a besoin d’ajouter useCallback
à l’import en haut du fichier avant d’encapsuler chacune de ces fonctions.
Lorsque l’utilisateur soumet le formulaire, on veut pouvoir envoyer le nom d’utilisateur et le nom du salon au serveur en échange d’un access token que l’on pourra utiliser pour entrer sur le salon. On va aussi créer cette fonction dans le composant.
On utilisera l’API fetch pour envoyer les données au format JSON au endpoint, recevoir et parser la réponse, puis on utilisera setToken
afin de stocker le token dans notre état. On encapsulera aussi cette fonction avec useCallback
, mais dans ce cas, la fonction dépendra du username
et du roomName
, donc ajoutons-les comme dépendances de useCallback
.
Pour la dernière fonction de ce composant, nous allons ajouter une fonctionnalité logout qui éjectera l’utilisateur d’un salon et les redigera sur le lobby. Pour ce faire, nous allons configurer le token sur null
. et on l’encapsulera dans useCallback
sans dépendances.
Ce composant dirige majoritairement les composants sous lui, donc il n’y a pas grand chose à afficher avant de les avoir créer. Créons d’abord le composant Lobby qui affiche le formulaire demandant un nom d'utilisateur et le nom du salon.
Le composant Lobby
Créons un nouveau fichier dans src/Lobby.js
. Ce composant n’a besoin de stocker aucune donnée comme il passera tous les éléments à son confrère, le composant VideoChat. Quand le composant est rendu, il recevra le username
et le roomName
ainsi que les fonctions pour s’occuper de changer chacune d’entre elles et de soumettre le formulaire. On peut déstructurer ces props pour faciliter leur utilisation plus tard.
La tâche principale du composant Lobby
est d’afficher le formulaire en utilisant les props comme suit :
Mettons à jour le composant VideoChat
pour afficher le Lobby
- à moins que nous ayons un token
, auquel cas on affichera le username
, roomName
et token
. Nous avons besoin d’importer le composant Lobby
en haut du fichier et de rendre un peu de JSX au bas de la fonction composant :
Pour avoir ce rendu sur la page, nous avons aussi besoin d’importer le composant VideoChat
dans le composant App
et l’afficher. Ouvrons src/App.js
à nouveau et effectuons les changements suivants :
Assurons-nous que l’application s’exécute toujours (ou relançons-la avec npm run dev
). Ouvrons la dans le navigateur et on verra un formulaire. Il faut alors remplir les infos de nom d’utilisateur et de salon, cliquer sur submit puis la vue devrait changer pour nous afficher les noms choisis en plus du token récupéré par le serveur.
Le composant Room
Maintenant que nous avons ajouté un nom d’utilisateur et de salon à l’application, on peut s’en servir pour rejoindre un salon de discussion Vidéo Twilio. Pour travailler avec le service Twilio Video, on aura besoin de JS SDK. Pour cet exemple, nous allons travailler avec la version Twilio Video 2.2.0. Installons-la avec :
Créons un nouveau fichier appelé Room.js
dans le répertoire src
. Lancez-le avec le boilerplate suivant.
On aura besoin d’utiliser le SDK Twilio Video dans ce composant ainsi que les hooks useState
et useEffect
. Nous allons aussi prendre comme props les roomName
, token
, et handleLogout
du composant parent VideoChat
.
La première chose que le composant va faire est de se connecter au service Twilio Video en utilisant le token et le nom du salon. Une fois que nous sommes connectés, nous obtenons un objet room
que nous voulons stocker. Le salon comporte aussi une liste de participants qui changera au fur et à mesure, donc nous les stockerons également. Pour ce faire, on utilisera useState
, les valeurs initiales seront null
pour le salon, et un tableau vide pour les participants:
Avant de rejoindre le salon, rendons quelque chose pour ce composant. On pourra alors parcourir le tableau pour rendre visible l’identité de chaque participants et aussi montrer l’identité du local des participants dans le salon:
Mettons à jour le composant VideoChat
pour afficher ce composant Room
à la place des informations placeholder que l’on avait tout à l’heure.
En exécutant ce code dans le browser, le nom du salon et le bouton de déconnexion s’afficheront mais nous n’aurons pas l’identité des participants car nous n’avons pas encore connecté et rejoint le salon.
Nous avons toutes les informations nécessaires pour rejoindre le salon, donc nous devrions déclencher l’action de connexion dès le premier render du composant. On veut aussi pouvoir quitter le salon une fois que le composant est détruit (aucun intérêt à garder une connexion WebRTC en arrière-plan). Ce sont tous les deux des effets secondaires.
Avec des composants de classe, on utiliserait les méthodes de cycle de vie componentDidMount
et componentWillUnmount
. Avec React Hooks, on utilisera le hook useEffect.
useEffect
La fonction useEffect
prend une méthode et l’exécute une fois que le composant s’est affiché. Lorsque notre composant charge, on veut pouvoir se connecter au service vidéo. On aura aussi besoin des fonctions que l’on peut exécuter dès qu’un participant rejoint ou quitte le salon, pour les ajouter et les enlever du state.
Commençons à coder notre hook en ajoutant ce code avant le JSX dans Room.js
:
Le code utilise le token
et le roomName
pour se connecter au service Twilio Video. Une fois que la connexion est établie, on configure l’état du salon et on met en place un Listener pour les autres participants qui se connectent ou se déconnectent. Puis à l’aide de la fonction participantConnected
que nous avons codé plus tôt, on passe dans une boucle sur la liste des participants pour les rajouter au tableau des participants
C’est un bon début mais si l’on enlève le composant, nous serons toujours connectés à la room. Donc nous avons besoin de nettoyer derrière nous aussi.
Si nous retournons une fonction depuis le callback que nous passons à useEffect
, elle sera exécutée quand le composant sera démonté. Quand un composant qui utilise useEffect
est rendu, cette fonction est également appelée pour nettoyer l’effet avoir qu’il ne soit exécuté à nouveau.
Retournons donc une fonction qui arrêtera le suivre de toutes les pistes des participants et puis les déconnectera du salon, si le participant est localement connecté:
Notez que l’on utilise ici la version callback de la fonction setRoom
que l’on a récupérée de useState
tout à l’heure. Si vous passez une fonction sur setRoom
, alors elle sera appelée avec les valeurs précédentes - dans notre cas, le salon existant que l’on appellera currentRoom
- et cela configurera l’état à ce que nous rendrons.
Malgré tout ça, on n’a pas encore tout à fait fini. Dans son état actuel, le composant quittera et rejoindra le salon à chaque fois qu’il est rendu à nouveau. Ce n’est pas l’idéal, donc nous avons besoin de lui dire quand il devrait nettoyer et exécuter l’effet à nouveau. Un peu comme avec useCallback
, on peut le faire en passant un tableau de variables dont l’effet dépend. Si les variables ont changé, on veut d’abord pouvoir nettoyer, puis exécuter l’effet à nouveau. Si elles n’ont pas changé, il n’y pas besoin d’encore exécuter l’effet.
En regardant la fonction, on peut voir que là où il y a le roomName
ou token
à changer, on s’attend à se connecter à un salon différent, ou comme un utilisateur différent. On va aussi passer ces variables sous forme de tableau à useEffect
:
Notez que nous avons deux fonctions de rappel définies au sein de cet effet. Vous pourriez penser qu’elles devraient être wrappées dans useCallback
, comme nous l’avons fait plus tôt, mais ce n’est pas le cas. Comme elles font partie de l’effet, elles ne s’exécuteront que lorsque les dépendances se mettront à jour. Aussi, vous ne pouvez pas utiliser de hooks à l’intérieur des fonctions de rappel, elles doivent être utilisées directement au sein des composants ou d’un hook personnalisé.
On a plus ou moins fini avec ce composant, mais regardons d’abord si il fonctionne. Rechargeons/rafraichissons l’application et entrons un nom d’utilisateur et un nom de salon. On devrait voir notre identité apparaître alors qu’on rejoint le salon. Cliquer sur le bouton de déconnexion nous ramènera directement au lobby.
La pièce finale du puzzle est d’afficher les participants dans l’appel vidéo, ajouter leur vidéo et audio à la page.
Le composant Participant
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:
Créez un nouveau composant appelé Participant.js
dans src
. On commencera avec notre boilerplate habituelle bien que nous utiliserons trois hooks dans ce composant : useState
et useEffect
que l’on connaît déjà, et useRef
. On va passer un objet participant
dans les props et on gardera la trace des pistes vidéos et audios des participants avec useState
:
Pour l’instant, affichons le jsx que nous voulons. Pour lier l’élément JSX a la ref, nous utilisons l’attribut ref
.
J’ai aussi réglé les attributs des tags <video>
et <audio>
sur autoplay (afin qu’ils se lancent dès qu’ils ont un stream média) et sur muted (pour éviter de finir sourd avec les retours pendant la phase de test, vous me remercierez si jamais vous faites cette erreur).
Ce composant ne peut pas faire grand chose sans utiliser d’effets. On utilisera donc trois fois le hook useEffect
dans ce composant, vous comprendrez vite pourquoi.
Le premier hook useEffect
va configurer les pistes vidéo et audio dans le state et paramétrer les Listeners dans l’objet participant, pour lorsque les pistes sont ajoutées ou retirées. useEffect
aura également besoin de nettoyer et retirer ces Listeners et vider l’état lorsque que le composant est démonté.
Nous ajouterons deux fonctions dans notre premier hook useEffect
. Elles s’exécuteront soit quand une piste de participant est ajoutée, soit quand elle est retirée. Ces deux fonctions vérifient si la piste est un audio ou une vidéo, puis l’ajoutent ou la retirent de l’état en utilisant la fonction de state pertinente.
Ensuite, nous utilisons l’objet participant pour configurer les valeurs initiales des pistes audio et vidéo. L’objet a des propriété videoTracks
et audioTracks
qui renvoient un Map des objets TrackPublication. Tant qu’il n’est pas inscrit, un TrackPublication
n’a pas accès à son objet track
. Nous avons donc besoin de filtrer toutes les pistes qui n’existent pas. Pour ce faire, on utilisera la fonction qui map depuis TrackPublication
vers Track
et on filtrera les pistes qui sont null
.
Ensuite, on installe les Listeners aux événements trackSubscribed
et trackUnsubscribed
, en utilisant les fonctions que l’on vient d’écrire, avant de faire le nettoyage dans la fonction renvoyée :
Notez que le hook dépend uniquement de l’objet participant
et ne sera pas nettoyé et ré-exécuté à moins que le participant ne change.
Nous avons aussi besoin d’un hook useEffect
pour attacher les pistes audio et vidéo au DOM. Je ne vous en montre qu’un ici, la version vidéo, mais c’est exactement le même processus pour l’audio, il faut juste entrer “audio” à la place de “video”.
Le hook va d’abord obtenir la première piste vidéo à partir de l’état et, si il existe, l’attacher au node DOM courant que nous avons pris avec un ref tout à l’heure. Vous pouvez vous référer au node DOM actuel dans le ref en utilisant videoRef.current
. Si l’on attache la piste vidéo, on aura aussi besoin de rendre une fonction pour le détacher lors du nettoyage.
Utilisez ce hook à nouveau pour audioTracks
et nous voilà prêts à rendre notre composant Participant
à partir du composant Room
. Importons le composant Participant
en haut du fichier, puis remplaçons les paragraphes qui affichaient l’identité avec le composant en lui-même.
Maintenant, nous pouvons recharger l’application, joindre un salon, et l’on se verra sur l’écran ! Si on ouvre un autre browser et que l’on rejoint le même salon, on se verra à nouveau sur l’écran. Si on clique sur le bouton de déconnexion, on sera renvoyés dans le lobby.
Conclusion
Construire avec Twilio Video dans React demande un peu plus de travail parce qu’il y a plein de sortes d’effets secondaires à gérer. Faire une requête pour avoir le token, se connecter au service Vidéo et manipuler le DOM pour connecter les éléments <video>
et <audio>
, il y a de quoi s’occuper. Dans cet article, nous avons vu comment utiliser useState
, useCallback
, useEffect
et useRef
pour contrôler ces effets secondaires et développer notre application en se servant uniquement de composants fonctionnels.
Avec un peu de chance, ce tutoriel vous aidera à comprendre le fonctionnement de Twilio Video et React Hooks. Tout le code source de l’appli est disponible sur GitHub pour que vous vous amusiez à démonter et reconstruire cette application.
Pour plus de lecture sur les ReactHooks, vous pouvez lire la documentation officielle, cette visualisation sur comment penser en ReactHooks et consulter l’article détaillé de Dan Abramov sur useEffect
, c’est un post long mais qui vaut le coup, promis.
Si vous voulez en savoir plus sur ce que l’on peut construire avec Twilio Vidéo, lisez ces posts sur comment changer de caméra lors d’un chat vidéo ou ajouter un partage d’écrans à votre vidéochat.
Si vous construisez ces projets ou autre chose de cool qui embarque de la vidéo avec React, faites le moi savoir dans les commentaires, sur Twitter ou par email à philnash@twilio.com.
Articles associés
Ressources connexes
Twilio Docs
Des API aux SDK en passant par les exemples d'applications
Documentation de référence sur l'API, SDK, bibliothèques d'assistance, démarrages rapides et didacticiels pour votre langage et votre plateforme.
Centre de ressources
Les derniers ebooks, rapports de l'industrie et webinaires
Apprenez des experts en engagement client pour améliorer votre propre communication.
Ahoy
Le hub de la communauté des développeurs de Twilio
Meilleures pratiques, exemples de code et inspiration pour créer des expériences de communication et d'engagement numérique.