Artículo original: How to Build a Real-time Chat App with ReactJS and Firebase
En este artículo, voy a mostrarte como crear una app de chat en tiempo real usando React.js y Firebase.
En la app, vamos a permitir que los usuarios se logueen con su cuenta de Google usando el servicio de inicio de sesion de Firebase. También guardaremos y recuperaremos todos los mensajes usando el servicio Firestone de Firebase.
Prerrequisitos
Para este tutorial, deberás tener instalado Node.js en tu sistema, además deberás tener conocimientos intermedios de CSS, JavaScript y ReactJS y conocimientos sobre el uso de la terminal de líneas de comandos. No debes saber utilizar Firebase para hacer el mismo.
¿Que es Firebase?
Firebase es un BaaS (Backend as a Service), una plataforma de backend propiedad de Google que permite a desarrolladores construir aplicaciones para iOS, Android o aplicaciones web.
Nos provee con herramientas para el seguimiento de análisis, reportar y solucionar fallas de aplicaciones y crear pruebas de marketing y productos. Esto, ayuda a los desarrolladores a crear aplicaciones de calidad, hacer crecer su base de usuarios y obtener ganancias.
Para este tutorial, vamos a utilizar dos herramientas: Firebase Authentication y Cloud Firestone.
Firebase Authentication
Firebase Authentication (SDK) es una herramienta de Firebase que permite realizar autenticación de usuarios mediante varios métodos, como contraseñas, números de telefono, cuentas de Google, Facebook, Twitter, GitHub y mas. En este proyecto usaremos la autenticación a través de cuentas de Google.
Cloud Firestore
Cloud Firestones es un servicio de bases de datos no relacionales que nos permite guardar y sincronizar información. Este servicio guarda la información en documentos como pares de clave-valor, y estos documentos en colecciones.
Los documentos, a su vez pueden tener sub-colecciones, permitiéndonos anidar colecciones dentro de otras colecciones, además la información se sincroniza automáticamente entre todos los dispositivos que estén consultando esos datos.
Ahora que ya tienes una idea general de como trabajan Firebase y Cloud Firestone, vamos a trabajar en el proyecto.
Nota: para este proyecto, ya escribí el CSS y preconstruí los componentes para la aplicación de chat, puedes encontrar el código del proyecto final en GitHub y los componentes junto con el CSS en la carpeta "setup", y también puedes ver el proyecto final funcionando en este link
Como crear nuestra aplicación de React
Clona este repositorio de GitHub, borra la carpeta src en el directorio raíz y remplázala con la carpeta "src" que se encuentra en la carpeta "setup".
Alternativamente, puedes crear tu aplicación de React corriendo el comando create-react-app en la terminal.
npx create-react-app react-chat

react-chat
es el nombre de la aplicación. Una vez que finaliza correremos el comando npm install firebase react-firebase-hooks
para instalar firebase y react-firebase-hooks.

Una vez que finaliza, borra la carpeta src y reemplazala con la que descargaste de la carpeta setup para usar el archivo CSS y los componentes preconstruidos. (Opcionalmente, puedes escribir tus propios componentes y tu propio archivo CSS)
Ahora, tu carpeta src contiene los siguientes componentes:
- Una carpeta de componentes con un componente NavBar que contiene los botones de Google sign-in y Sign Out,
- Un componente que le da la Bienvenida al usuario que no está logueado,
- Un componente Chatbox que solo es visible cuando el usuario está logueado.
- El componente Message para mostrar los mensajes de los usuarios,y
- El componente SendMessage que usaremos para que el usuario pueda escribir y enviar sus mensajes.

Además, tenemos lo siguiente:
- Una carpeta img donde se guarda la imagen de Google sign-in y sign-out,
- un archivo CSS llamado App.css con el código CSS para la aplicación,
- un archivo llamado App.js con todos los componentes importados dentro,
- y un archivo llamado index.js.
Corre el comando npm start
para ver la aplicación en el navegador, deberías ver algo como esto:

Ahora, creemos una cuenta de Firebase y configuremos nuestro proyecto.
Como configurar el proyecto en Firebase
Si todavía no tienes una cuenta de Firebase, puedes abrir una usando tu correo de Gmail (solo se puede usar el mail de Google).
En la página de inicio, haga clic en comenzar y luego en crear un proyecto.


Completa el formulario para crear el proyecto agregando un nombre de proyecto (en nuestro caso usaremos el nombre React-chat). Si quieres dejar habilitado Google Analytics para tu proyecto déjalo, sino deshabilítalo. Después, haz clic en crear proyecto

Una vez creado, presiona el botón Continuar.

Elige el tipo de aplicación al que quieres agregar Firebase, para este artículo elegiremos el icono de código porque estamos desarrollando una aplicación web.

Ingresa un sobrenombre para la aplicación y haz clic en el botón registrar app.

En el menú selecciona npm, copia el código (lo vamos a utilizar más tarde) y haz clic en ir a la consola.

Cómo configurar Firebase Authentication
Para configurar Firebase Authentication, ve al menú de la izquierda y presiona sobre Compilación y selecciona Authentication del desplegable.

Haz clic en comenzar y selecciona a Google en el menú proveedores de acceso.

Presiona sobre el botón de la derecha para habilitar el proyecto y selecciona un correo de soporte, luego haz clic en guardar.

Cómo configurar Cloud Firestone
Nuevamente. ve al menú de la izquierda, clickea en compilación y selecciona Firestone. Luego haz clic en crear base de datos y completa el formulario.

En el primer paso, selecciona la localización de la base de datos, por defecto el sistema asignará la ubicación más cercana a la que te encuentras, luego presiona siguiente.

Selecciona el modo de la base de datos, puedes elegir producción o modo de prueba.
El modo de prueba permite que cualquier cliente puede leer o escribir en la base de datos por 30 días. El modo producción significa que nadie puede leer o escribir en la base de datos, por lo que debes escribir tus reglas para brindar acceso a la base de datos.

Selecciona modo de producción y presiona el botón crear.
El siguiente paso será editar nuestras reglas, haz clic en la pestaña reglas.


Reemplaza la regla que esta en la pestaña por la siguiente y presiona el boton publicar.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read: if true;
allow create, update, delete, write: if request.auth != null;
}
}
}
Analicemos el código, la primer parte, allow read: if true;
significa que cualquiera puede leer tu base de datos, mientras que el bloque de código siguiente allow create, update, delete, write: if request.auth != null;
significa que solo los clientes autenticados pueden crear, actualizar, borrar y escribir datos en la base de datos.
Puedes comenzar agregando o creando una colección en tu base de datos o crear una automáticamente en la aplicación que estamos desarrollando, lo que haremos después. Si quieres crear una colección en Cloud Firestone, vuelve a la pestaña de datos presionando en la misma y presiona el botón iniciar coleccion.
Ingresa el nombre de la colección, por ejemplo "messages" y clickea en siguiente.

Ahora, crea el documento para la colección,

Clickea en ID automático, de esa forma el sistema generará el ID de forma automática, o si lo prefieres ingresa uno tu.
Luego, se crea el par clave->valor para el documento. El campo llamado "Campo" representa el nombre de la clave, el campo "Tipo" nos dice que valor almacena (cadena de texto, number, timeStamp, etc), y finalmente el campo "Valor" es lo que guardamos en esta clave.
Puedes seguir agregando más documentos presionando en el enlace "Agregar campo", sino presiona en Guardar para que se guarde la colección.
Nuestro proyecto de Firebase ya esta correctamente seteado, volvamos a la aplicación de React.
Cómo configurar Firebase en React
En la carpeta src, crea un archivo llamado firebase.js
y pega el código que copiamos al configurar firebase.
Tambien importemos los servicios getAuth
y getFirestore
desde Autenticación para Web y Firestore en la nube para web respectivamente. Puedes leer más sobre las bibliotecas disponibles para Firebase en la página de la documentación.
Nuestro archivo firebase.js debe verse asi:
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: LA_CLAVE_PARA_LA_APLICACION_VA_AQUI(CLAVE_API),
authDomain: EL_DOMINIO_DE_AUTENTICACION_PARA_LA_APP_VA_AQUI,
projectId: EL_ID_DEL_PROYECTO_PARA_LA_APP_VA_AQUI,
storageBucket: EL_BUCKET_PARA_LA_APLICACION_VA_AQUI,
messagingSenderId: EL_ID_PARA_ENVIAR_LOS_MENSAJES_VA_AQUI,
appId: EL_APP_ID_DE_LA_APLICACION_DE_REACT_VA_AQUI,
};
Cómo implementar Firebase dentro de nuestra aplicación React
Cómo autenticar usuarios con su cuenta de Google
Nosotros queremos que los usuarios tengan acceso a la aplicación de chat y que en caso de que estén autenticados puedan enviar mensajes, si no lo están queremos que vean la página de bienvenida para poder loguearse usando el botón de Google sign-in, y si esta logueado debe poder ver el botón de Sign-out para salir del chat.
Esta autenticación será controlada por el componente NavBar, que contiene ambos botones (sign-in y sign-out).
En nuestro componente NavBar
, importaremos nuestra imagen de Google "sign-in", y la guardaremos como la constante llamada GoogleSignin
. También tendremos un estado llamado user
, seteado como falso, una función llamada googleSignIn
que setea el estado de user
como verdadero, y una función llamada signOut
que setea el estado de user
como falso.
También tenemos un elemento nav
con un tag h1
que representa el título de nuestra app y dos botones que se renderizan condicionalmente en base al estado del componente user
import React, { useState } from "react";
import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png";
const NavBar = () => {
const [user, setUser] = useState(false);
const googleSignIn = () => {
setUser(true);
};
const signOut = () => {
setUser(false);
};
return (
<nav className="nav-bar">
<h1>React Chat</h1>
{user ? (
<button onClick={signOut} className="sign-out" type="button">
Sign Out
</button>
) : (
<button className="sign-in">
<img
onClick={googleSignIn}
src={GoogleSignin}
alt="sign in with google"
type="button"
/>
</button>
)}
</nav>
);
};
export default NavBar;
Hagamos un cambio en el componente NavBar, importando lo siguiente:
import { auth } from "../firebase";
import { useAuthState } from "react-firebase-hooks/auth";
import { GoogleAuthProvider, signInWithRedirect } from "firebase/auth";
Reemplaza el estado del usuario con el siguiente código:
const [user] = useAuthState(auth);
Y edita las funciones googleSignIn y signOut:
const googleSignIn = () => {
const provider = new GoogleAuthProvider();
signInWithRedirect(auth, provider);
};
const signOut = () => {
auth.signOut();
};
La función useAuthState
se dispara en el momento que el usuario se loguea o desloguea, permitiéndonos acceder a los detalles del usuario. Actualmente, el estado del usuario es null
, una vez que te logueas, el estado del usuario cambiará a la información suministrada por el método de autenticación (en este caso, Google).
En la función googleSignIn
, le indicamos a Firebase que el el usuario quiere autenticarse con Google usando el GoogleAuthProvider()
. También lo redirige a la página de inicio de sesión de Google.
Luego de el correcto inicio de sesión del usuario, sus datos son almacenados en auth
, y el usuario es redireccionado a la app. La función signOut
borra la información almacenada de autenticación, retornando ésta a null
. El nuevo estado almacenado también determina qué botón debe renderizarse.
Agreguemos también autenticación a nuestro archivo App.js. Importa lo siguiente:
import { auth } from "./firebase";
import { useAuthState } from "react-firebase-hooks/auth";
Agrega el nuevo user state, para que podamos renderizar el componente Welcome si el usuario no está logueado o el componente Chatbox si el usuario ya se autenticó.
const [user] = useAuthState(auth);
El codigo final debe lucir así:
import { auth } from "./firebase";
import { useAuthState } from "react-firebase-hooks/auth";
import "./App.css";
import NavBar from "./components/NavBar";
import ChatBox from "./components/ChatBox";
import Welcome from "./components/Welcome";
function App() {
const [user] = useAuthState(auth);
return (
<div className="App">
<NavBar />
{!user ? <Welcome /> : <ChatBox />}
</div>
);
}
export default App;
Al probar nuestras nuevas funciones de sign-in y sign-out, veremos lo siguiente:

Ahora, hagamos lo mismo con el componente Welcome, que ahora tiene el siguiente código:
import React from "react";
import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png";
const Welcome = () => {
const googleSignIn = () => {};
return (
<main className="welcome">
<h2>Welcome to React Chat.</h2>
<img src="/logo512.png" alt="ReactJs logo" width={50} height={50} />
<p>Sign in with Google to chat with with your fellow React Developers.</p>
<button className="sign-in">
<img
onClick={googleSignIn}
src={GoogleSignin}
alt="sign in with google"
type="button"
/>
</button>
</main>
);
};
export default Welcome;
Importaremos lo siguiente:
import { auth } from "../firebase";
import { GoogleAuthProvider, signInWithRedirect } from "firebase/auth";
y tambien editaremos la funcion googleSignIn:
const googleSignIn = () => {
const provider = new GoogleAuthProvider();
signInWithRedirect(auth, provider);
};
Ahora, podremos loguearnos desde el botón que se renderiza en el componente Welcome:

Como enviar y almacenar mensajes en Firebase:
Actualmente, estamos mostrando un mensaje de prueba de nuestro componente Message y, el botón Send no realiza ninguna acción. Cuando ingresamos un mensaje y pulsamos el botón Send, queremos que el mensaje se envíe de inmediato a la aplicación.
Así, que editemos el componente SendMessage:
Primero, importamos useState
de React, auth
y db
de nuestro archivo de configuración de firebase, y addDoc
, collection
y serverTimestamp
de la librería Firestore.
import React, { useState } from "react";
import { auth, db } from "../firebase";
import { addDoc, collection, serverTimestamp } from "firebase/firestore";
Crearemos un estado llamado message
que inicialmente esté definido como una cadena vacía y se pase como valor a la etiqueta input
. La funcion onChange
es también agregada a el input, que setea el estado del message
en lo que el usuario tipee.
const SendMessage = () => {
const [message, setMessage] = useState("");
return (
<form className="send-message">
<input
...
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit">Send</button>
</form>
);
};
También creamos una función llamada sendMessage
, y agregamos el atributo onSubmit
en nuestro formulario, que ejecuta la función sendMessage
cuando el usuario clickea en el botón Send
(enviar). Nótese que el botón debe tener el tipo type="submit"
para que la función funcione.
const sendMessage = async (event) => {
event.preventDefault();
if (message.trim() === "") {
alert("Enter valid message");
return;
}
const { uid, displayName, photoURL } = auth.currentUser;
await addDoc(collection(db, "messages"), {
text: message,
name: displayName,
avatar: photoURL,
createdAt: serverTimestamp(),
uid,
});
setMessage("");
};
return (
<form onSubmit={(event) => sendMessage(event)} className="send-message">
...
La función sendMessage
es una funcion asincrona. Primero chequea si el el usuario esta intentando enviar una cadena vacía o espacios vacíos como un mensaje y, en caso de que así sea alerta al usuario.
Si el mensaje no es una cadena vacía, toma del usuario los siguientes datos dela información proporcionada por auth
al logguearse: uid, displayName y photoURL. Estos datos corresponden con el identificador único del usuario, su nombre y la URL de la foto del usuario, respectivamente.
Luego, usa la función addDoc()
para crear un documento dentro de la colección messages de nuestra base de datos, a la que accede gracias al archivo que importamos. En caso de que la colección no exista, crea una por nosotros.
También crea un par de llave-valor, guardando nuestro mensaje en text, displayName en name, guardando el momento en el que el mensaje fue guardado en nuestra base de datos en la columna createAt, y el uid.
Estos pares de llave-valor, conforman los datos de nuestro documento. Una vez que se completa la acción, se restablece el estado del mensajea una cadena vacía.
Como recuperar mensajes de nuestra base de datos
Luego de enviar el mensaje del usuario, debemos mostrarlo en la pantalla. Vayamos a nuestro componente chatBox e importemos lo siguiente:
import { useEffect, useRef, useState } from "react";
import {
query,
collection,
orderBy,
onSnapshot,
limit,
} from "firebase/firestore";
import { db } from "../firebase";
Creamos el hook useEffect
que correra cada vez que se realizan cambios en el thatroom, por ejemplo enviar o borrar un mensaje.
useEffect(() => {
const q = query(
collection(db, "messages"),
orderBy("createdAt", "desc"),
limit(50)
);
const unsubscribe = onSnapshot(q, (QuerySnapshot) => {
const fetchedMessages = [];
QuerySnapshot.forEach((doc) => {
fetchedMessages.push({ ...doc.data(), id: doc.id });
});
const sortedMessages = fetchedMessages.sort(
(a, b) => a.createdAt - b.createdAt
);
setMessages(sortedMessages);
});
return () => unsubscribe;
}, []);
En el hook useEffect
, tenemos la constante q
, una consulta de Firebase que busca los mensajes dentro de la base de la colección messages de nuestra base de datos, luego ordena los documentos obtenidos basados en la llave createAt y devuelve un máximo de 50 documentos (mensajes guardados)
La constante unsubscribe
representa la función onSnapshot
, que escucha los cambios en el documento, esta función tiene un arreglo vacío llamado messages
.
El bucle forEach
itera entre todos los documentos de la colección y guarda la información en un nuevo arreglo. Luego, asigna el arreglo inicial de mensajes al nuevo arreglo de mensajes.
También usamos el método map en nuestro arreglo de mensajes para cada mensaje/documento en nuestro componente message.
{messages?.map((message) => (
<Message key={message.id} message={message} />
))}
El codigo completo luce asi:
import React, { useEffect, useRef, useState } from "react";
import {
query,
collection,
orderBy,
onSnapshot,
limit,
} from "firebase/firestore";
import { db } from "../firebase";
import Message from "./Message";
import SendMessage from "./SendMessage";
const ChatBox = () => {
const [messages, setMessages] = useState([]);
const scroll = useRef();
useEffect(() => {
const q = query(
collection(db, "messages"),
orderBy("createdAt", "desc"),
limit(50)
);
const unsubscribe = onSnapshot(q, (QuerySnapshot) => {
const fetchedMessages = [];
QuerySnapshot.forEach((doc) => {
fetchedMessages.push({ ...doc.data(), id: doc.id });
});
const sortedMessages = fetchedMessages.sort(
(a, b) => a.createdAt - b.createdAt
);
setMessages(sortedMessages);
});
return () => unsubscribe;
}, []);
return (
<main className="chat-box">
<div className="messages-wrapper">
{messages?.map((message) => (
<Message key={message.id} message={message} />
))}
</div>
{/* when a new message enters the chat, the screen scrolls down to the scroll div */}
<span ref={scroll}></span>
<SendMessage scroll={scroll} />
</main>
);
};
export default ChatBox;
Vayamos a nuestro componente message, y rendericemos los datos recibidos en el navegador.
import React from "react";
import { auth } from "../firebase";
import { useAuthState } from "react-firebase-hooks/auth";
const Message = ({ message }) => {
const [user] = useAuthState(auth);
return (
<div
className={`chat-bubble ${message.uid === user.uid ? "right" : ""}`}>
<img
className="chat-bubble__left"
src={message.avatar}
alt="user avatar"
/>
<div className="chat-bubble__right">
<p className="user-name">{message.name}</p>
<p className="user-message">{message.text}</p>
</div>
</div>
);
};
export default Message;
Importamos auth
y useAuthState
, y almacenamos los datos del usuario en user
. Deconstruimos el prop
message y asignamos el avatar al atributo src
del componente img. También reemplazamos el nombre y el mensaje "de prueba" con el mensaje obtenido desde message.
También aplicamos una condición al estilo CSS según el uid
del autor del mensaje, entonces si el uid
del autor del mensaje es el mismo que el del usuario logueado, se aplica al div el estilo definido en el selector derecho; en caso contrario no se agrega ningún estilo adicional.
Actualmente, todos los mensajes se posicionan a la izquierda, así que si el autor del mensaje es el usuario logueado, sus mensajes se posicionaran a la derecha. Veamos esa dinámica en acción en el navegador:

El mensaje es enviado y almacenado en nuestra base de datos, luego todos los mensajes se recuperan y la sala es actualizada en tiempo real con los mensajes nuevos.
El nombre y el avatar de cada usuario son mostrados en cada tarjeta, pero también podemos ver que el chat no se desliza automáticamente hacia abajo cuando llega un nuevo mensaje. Vamos a arreglar eso.
Como hacer que el chat se desplace hasta el final
Vayamos al archivo ChatBox.js, importemos el hook useRef
y creemos una constante llamada scroll:
import React, { useEffect, useRef, useState } from "react";
...
const scroll = useRef();
Crearemos un elemento span
con un atributo ref
conectado al valor de scroll,y , también pasamos el scroll en nuestro componente message.
Luego, creamos un elemento span
con un atributo ref
cuyo valor es scroll, y también pasamos scroll a nuestro componente SendMessage
:
<main className="chat-box">
...
{/* when a new message enters the chat, the screen scrolls dowwn to the scroll div */}
<span ref={scroll}></span>
<SendMessage scroll={scroll} />
</main>
Luego vamos al componente Messages, accedemos a la constante scroll, y agregamos scroll.current.scrollIntoView({ behavior: "smooth" })
al final de nuestra función sendMessage
.
Este código le indica al navegador que el scroll span debe ser visible después de enviar un mensaje. Por eso ponemos la etiqueta span
al final de cada mensaje.
const SendMessage = ({ scroll }) => {
const sendMessage = async (event) => {
...
setMessage("");
scroll.current.scrollIntoView({ behavior: "smooth" });
};
...
};
Volviendo al navegador, deberíamos ver que el chat se desplaza automáticamente hasta el final cuando del usuario envía un mensaje.

Como agregar dominios autorizados
Cuando desplegamos nuestra aplicacion en React, es indispensable agregar nuestro dominio a la lista de dominios autorizados en Firebase. Este paso, asegura que nuestra app se comunique correctamente con los servicios de Firebase, veamos como hacerlo:
En la consola de Firebase, navega hasta la sección Autenticación y haz click en la pestaña de configuración. Deslízate hasta la parte de Dominios autorizados; a continuación clickea en el botón agregar un dominio. Luego agrega el o los dominios donde la aplicación será desplegada.
Por ejemplo, si desplegaras la aplicación en la dirección https://my-react-chat-app.com, ingresa my-react-chat-app.com
como un dominio autorizado, y clickea el boton agregar para guardar los cambios.

Agregando el dominio donde desplegaras tu app a la lista de dominios autorizados, le brindas permisos a los servicios de Firestone para ser accedidos desde ese dominio. Si no hiciéramos esto, encontraríamos errores al intentar establecer conexión o realizar operaciones con Firebase.
Para terminar
Y, así es como llegamos al final de la construcción de esta aplicación de chat en tiempo real, ¡felicitaciones!.
En este tutorial, aprendimos a usar Firebase y React para crear una aplicacion de chat en tiempo real. También autenticamos a los usuarios usando el método de logueo por cuenta de Google de Firetone y, guardamos los mensajes del chat usando Cloud Firestore. Finalmente, aprendimos a usar algunos servicios y librerías de Firestone.
Puedes encontrar el código de este proyecto en GitHub y puedes probar la aplicación desplegada usando el siguiente enlace.
Si disfrutaste este articulo, por favor compártelo para ayudar a otros desarrolladores, también puedes visitar mi blog para leer mas artículos o puedes seguirme en Twitter o LinkedIn.
Hasta la próxima, byeeeeee!
