En este tutorial, te mostraremos cómo crear una aplicación de chat en tiempo real utilizando React y Vite, así como un backend simple con Node. Utilizaremos SocketIO para la comunicación en tiempo real y Tailwind CSS para el estilo.
Primero, vamos a crear una nueva carpeta llamada real-time-chat
. En esta carpeta, configuraremos un proyecto de React con Vite.
npx create-vite@latest client
cd client
npm install
Nota: Realiza la instalación seleccionando “React” y luego “JavaScript”.
Instalación de dependencias
Para este proyecto, utilizaremos varias bibliotecas, incluidas socket.io
y tailwindCSS
. Ejecuta lo siguiente en la carpeta client
que acabas de configurar:
npm install socket.io-client
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Configuración de Tailwind CSS
A continuación, configuraremos Tailwind CSS. El último comando que ejecutaste anteriormente debería haber generado un archivo tailwind.config.js
. Incluye lo siguiente:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
Luego, queremos editar el archivo src/index.css
, reemplazando todo con lo siguiente:
@tailwind base;
@tailwind components;
@tailwind utilities;
Ahora el proyecto está listo para usar Tailwind.
También puedes eliminar App.css
y la carpeta /assets
, ya que no las usaremos. Sin embargo, tendrás que recordar ir a App.js
y eliminar esas importaciones.
Configuración del servidor
Necesitamos un servidor simple para manejar las conexiones WebSocket. Vamos a configurar un servidor Express con SocketIO.
Crea un nuevo directorio en la carpeta real-time-chat
. Lo llamaremos server
.
mkdir server
cd server
npm init -y
npm install express socket.io
A continuación, crea un archivo index.js
en el directorio del servidor y agrega el siguiente código:
import express from 'express';
import http from 'http';
import { Server } from 'socket.io';
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: 'http://localhost:5173',
methods: ['GET', 'POST'],
},
});
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('chat message', (msg) => {
io.emit('chat message', msg);
});
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
Inicia el servidor escribiendo node index.js
en la terminal.
Nota: Reiniciar el servidor cada vez que realizas un cambio puede volverse molesto rápidamente. Si lo deseas, puedes instalar nodemon
escribiendo npm i --save-dev nodemon
. Ahora, en package.json
, puedes agregar un comando “start” y “dev” en “scripts”, de la siguiente manera:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
Construyendo el frontend
Ahora, construyamos el frontend de nuestra aplicación de chat, comenzando con el selector de emoji, que vamos a importar.
Instalemos la biblioteca emoji-picker-react
:
npm i emoji-picker-react
Ahora somos libres de usarlo:
/* A blank page with an emoji selector */
import Picker from 'emoji-picker-react';
function App() {
return (
<div>
<Picker />
</div>
);
}
export default App;
Aunque esto es solo un ejemplo y no es exactamente como se verá nuestro App.jsx
. De hecho, no queremos que el emoji se muestre así todo el tiempo, pero el usuario debería poder abrirlo y cerrarlo.
Entonces, en src
, vamos a crear un componente, EmojiPicker.jsx
. En este archivo, agrega lo siguiente:
import React, { useState, useRef, useEffect } from 'react';
import Picker from 'emoji-picker-react';
const EmojiPicker = ({ onEmojiClick }) => {
const [showPicker, setShowPicker] = useState(false);
const pickerRef = useRef(null);
const togglePicker = () => {
setShowPicker(!showPicker);
};
const handleClickOutside = (event) => {
if (pickerRef.current && !pickerRef.current.contains(event.target)) {
setShowPicker(false);
}
};
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
return (
<div className="relative" ref={pickerRef}>
<button
type="button"
className="p-2 bg-gray-200 rounded-full hover:bg-gray-300 focus:outline-none"
onClick={togglePicker}
>
😀
</button>
{showPicker && (
<div className="absolute bottom-12 right-0 z-10">
<Picker onEmojiClick={onEmojiClick} />
</div>
)}
</div>
);
};
export default EmojiPicker;
Configuración de los componentes del chat
Nuestra aplicación contendrá esencialmente tres componentes: ChatBox.jsx
, ChatMessages.jsx
, y ChatInput.jsx
.
Si observas los nombres de los componentes, podrás hacerte una idea de lo que hace cada uno de ellos. ChatBox.jsx
contendrá todo, es el componente principal. ChatMessages.jsx
contendrá la lógica para mostrar los mensajes que se manejarán con ChatInput.jsx
. Crea estos tres archivos en la carpeta src
.
Entonces, comencemos definiendo ChatBox.jsx
:
import React from 'react';
import ChatMessages from './ChatMessages';
import ChatInput from './ChatInput';
const ChatBox = () => {
return (
<>
<ChatMessages />
<ChatInput />
</>
);
};
export default ChatBox;
Actualmente, estos dos componentes no contienen ningún código, por lo que seguramente verás algunos errores provenientes de Vite.
No te preocupes, pega el siguiente código en ChatMessages.jsx
:
import React, { useEffect, useState, useRef } from 'react';
import { io } from 'socket.io-client';
const socket = io('http://localhost:3000');
const ChatMessages = () => {
const [messages, setMessages] = useState([]);
const messagesEndRef = useRef(null);
useEffect(() => {
socket.on('chat message', (msg) => {
setMessages((prevMessages) => [...prevMessages, msg]);
});
return () => {
socket.off('chat message');
};
}, []);
useEffect(() => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: 'smooth' });
}
}, [messages]);
return (
<div className="h-64 overflow-y-auto mb-4 p-4 bg-gray-100 rounded-lg">
{messages.map((msg, index) => (
<div
key={index}
className={`p-2 mb-2 rounded-lg text-left ${
index % 2 === 0 ? 'bg-blue-100' : 'bg-indigo-300'
}`}
>
{msg}
</div>
))}
<div ref={messagesEndRef} />
</div>
);
};
export default ChatMessages;
Hay muchas cosas sucediendo en este componente y probablemente podamos crear otro componente a partir de este, pero para los fines de este artículo, simplemente nos limitaremos a eso. El primer useEffect
configura un efecto secundario en el componente que escucha los mensajes de chat entrantes de la conexión WebSocket, actualizando el estado del componente, definido con const [messages, setMessages] = useState([])
, con los nuevos mensajes.
El segundo useEffect
básicamente se desplaza hasta el mensaje actual, según el useRef()
. El código que se incluye en return ()
ahora básicamente muestra los mensajes enviados.
En este punto, si eliminaras la llamada desde <ChatInput />
el componente ChatBox
, deberías ver algo como esto:
Componente ChatBox
Está completamente listo para aceptar algunos mensajes y mostrarlos, pero ahora necesitamos construir el ChatInput
componente.
import React, { useState } from 'react';
import { io } from 'socket.io-client';
import EmojiPicker from './EmojiPicker';
const socket = io('http://localhost:3000');
const ChatInput = () => {
const [message, setMessage] = useState('');
const sendMessage = (e) => {
e.preventDefault();
if (message.trim()) {
socket.emit('chat message', message);
setMessage('');
}
};
const handleEmojiClick = (emoji, event) => {
setMessage((prevMessage) => prevMessage + emoji.emoji);
};
return (
<form onSubmit={sendMessage} className="flex items-center">
<div className="m-2">
<EmojiPicker onEmojiClick={handleEmojiClick} />
</div>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
className="flex-grow p-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
placeholder="Type your message..."
/>
<button
type="submit"
className="p-2 bg-indigo-500 text-white rounded-r-lg hover:bg-indigo-600 focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
Send
</button>
</form>
);
};
export default ChatInput;
Este componente maneja el envío de mensajes, además de contener el <EmojiPicker />
componente, lo cual es una buena adición a la aplicación de chat basada en web.
El último paso es importar el Chat.jsx
componente a App.jsx
.
import React from 'react';
import Chat from './Chat';
function App() {
return (
<div className="flex flex-col items-center justify-center h-screen">
<div className="w-full max-w-md p-4 bg-white shadow-lg rounded-lg">
<Chat />
</div>
</div>
);
}
export default App;
A estas alturas ya deberías tener una aplicación de chat funcionando, que puedes probar abriendo otra pestaña del navegador y enviando mensajes entre ambas.
Conclusión
¡Ahora tienes una aplicación de chat en tiempo real bastante ordenada, ¿verdad?
Pero… ¿qué tal si podríamos mejorarla aún más? ¿Y si quisieras cancelar el envío de un mensaje o editarlo? Además, el estilo podría beneficiarse de algunos ajustes. Puedes optar por crear una aplicación de chat completa o mantenerla como un minicomponente para una aplicación más grande. ¡La decisión final es tuya!
Esperamos que esté tutorial te sea de ayuda, déjanos tu opinión en la caja de comentarios
Hola 👋 Soy Jawuil, un programador backend apasionado por la tecnología y el desarrollo de software. Me dedico a crear soluciones eficientes y escalables, siempre buscando mejorar y optimizar los sistemas que trabajo.
Además de mi carrera en programación, tengo una gran afición por la música. Aunque soy un músico amateur, disfruto explorando diferentes instrumentos y géneros, lo que me permite relajarme y expresar mi creatividad.
fanático del anime y las películas, me gusta mantener un equilibrio saludable entre el trabajo y el ocio .
Estoy siempre abierto a nuevas oportunidades y colaboraciones que me permitan crecer tanto profesional como personalmente.