Create a Group Chat with React Flask Socket.io
Learn how to create a group chat with React, Flask, and Socket.io.
Table of Contents 📖
- Project Demo
- Project Architecture
- Environment Variable Setup
- Python Project
- React Project Setup
- Gluing the DOM to React and Global Styles
- useWebSocket Hook
- Chat Bubble
- Chat Input
- Chat Box
- App Component
- Running the Application
Project Demo
Below is what we are going to build. When a user visits the provided URL, they will join a group chat. The style of the chat mimics that of iMessage. Sent messages are in blue on the right while received are in gray on the left.
[object Object],[object Object],[object Object]
Project Architecture
Below is the architecture of the project. Webpack will build the React application and serve it up to the client. The React app then forms a connection with the Flask server. As they are running on different origins, we will need to handle CORS as well.
Environment Variable Setup
ENV=development
WEBPACK_DEV_SERVER_PORT=1234
WEBPACK_DEV_SERVER_HOST=localhost
WEBPACK_DEV_SERVER_URL=http://localhost:1234
FLASK_PORT=1235
FLASK_HOST=localhost
FLASK_URL=http://localhost:1235
Python Project
python3 -m venv .venv
source .venv/bin/activate
pip install Flask flask-socketio py-mon python-dotenv
from flask import Flask, request
from flask_socketio import SocketIO, emit
from dotenv import load_dotenv
import os
# Take the environment variables from .env file
load_dotenv()
FLASK_HOST = os.environ.get("FLASK_HOST")
FLASK_PORT = os.environ.get("FLASK_PORT")
WEBPACK_DEV_SERVER_URL = os.environ.get("WEBPACK_DEV_SERVER_URL")
# Create Flask app
app = Flask(__name__)
# Wrap Flask app with SocketIO, creates a Flask-SocketIO server
# Specify webpack origin is allowed to connect to this server.
socket_io = SocketIO(app, cors_allowed_origins=WEBPACK_DEV_SERVER_URL)
# Handle connections
@socket_io.on("connect")
def connect():
# The request.sid is a unique ID for the client connection.
# It is added by SocketIO
print(f'Client connected: {request.sid}')
# Handle the data event. This is a user defined event. In other words,
# it is not reserved like connect and disconnect.
@socket_io.on("data")
def handle_data(data):
# Emits a SocketIO event to one or more connected clients.
# Broadcast True sends the message to all clients.
emit("data", {'socketId': request.sid, 'data': data}, broadcast=True)
# Handle disconnections
@socket_io.on("disconnect")
def disconnect():
print(f'Client disconnected: {request.sid}')
# Run the application at the provided host and port
if __name__ == "__main__":
socket_io.run(app, host=FLASK_HOST, port=FLASK_PORT)
React Project Setup
npm i socket.io-client
We install the client socket.io library on the client to connect to the Flask socket.io server.
Gluing the DOM to React and Global Styles
import {createRoot} from 'react-dom/client';
import App from './components/App';
import './styles/globals.css';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
* {
margin: 0;
padding: 0;
font-family: 'Roboto', sans-serif;
box-sizing: border-box;
}
:root {
--own-message: #218aff;
--message: #d8d8d8;
}
body {
height: 100vh;
width: 100vw;
}
useWebSocket Hook
import {useState, useEffect} from 'react';
import {io} from 'socket.io-client';
/**
* On mount, connect to the socket.io server
* @param {*} url
* @param {*} handleData
* @returns
*/
export default function useWebSocket(url, handleData) {
const [socket, setSocket] = useState(null);
useEffect(() => {
// Connect to the flask server
const socketClient = io(url);
socketClient.on('connect', () => {
console.log('Connected to server');
});
socketClient.on('disconnect', () => {
console.log('Disconnected from server');
});
// To make the hook more generic, accept a handleData
// function that is passed to the socket.
socketClient.on('data', (data) => {
handleData(data);
});
setSocket(socketClient);
// On unmount, close the socket
return () => {
socketClient.close();
}
}, []);
return socket
}
Chat Bubble
import * as styles from '../styles/ChatBubble.module.css'
/**
* The bubble that houses the message. The styles
* applied are different depending on who sent the message.
* @param {*} props
* @returns
*/
export default function ChatBubble(props) {
const {message, socketId} = props
const ownMessage = message.socketId === socketId
return (
<div className={styles.messageContainer}>
<p className={ownMessage ? styles.messageOwnUsername : styles.messageGuestUsername}>
{ownMessage ? 'You' : message.socketId}
</p>
<div
className={`
${ownMessage ? styles.ownMessage : styles.guestMessage}
${styles.message}`}
>
{props.message.data}
</div>
</div>
)
}
p {
color: var(--message);
font-size: x-small;
margin-bottom: 4px;
}
.messageContainer {
display: flex;
flex-direction: column;
}
.messageOwnUsername {
align-self: flex-end;
}
.messageGuestUsername {
align-self: flex-start;
}
.message {
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
width: fit-content;
}
.ownMessage {
display: inline-block;
align-self: flex-end;
background-color: var(--own-message);
color: white;
text-align: right;
}
.guestMessage {
align-self: flex-start;
background-color: var(--message);
}
Chat Input
import {useState} from 'react';
import * as styles from '../styles/ChatInput.module.css'
/**
* Accepts user input from the text input and then uses
* the provided socket to send the message to the server.
* The server then broadcasts it to all connected clients.
* @param {*} props
* @returns
*/
export default function ChatInput(props) {
const [message, setMessage] = useState('');
const sendMessage = () => {
props.socket.emit('data', message);
setMessage('');
}
return (
<div className={styles.chatInput}>
<input
value={message}
onKeyDown={(e) => e.key === 'Enter' && sendMessage()}
onChange={(e) => setMessage(e.target.value)}
type='text'
>
</input>
<button onClick={sendMessage}>Send</button>
</div>
)
}
input {
flex-grow: 1;
resize: none;
padding: 10px;
font-size: 16px;
border: 1px solid var(--message)
}
input:focus {
outline: none;
}
button {
padding: 10px;
font-size: 16px;
border: none;
cursor: pointer;
}
.chatInput {
width: 100%;
display: flex;
align-items: center;
gap: 8px;
}
Chat Box
import ChatBubble from "./ChatBubble"
import * as styles from '../styles/ChatBox.module.css'
/**
* Houses the chat bubbles that display the messages
* @param {*} props
* @returns
*/
export default function ChatBox(props) {
return (
<div className={styles.chatBox}>
{props.messages.map((message, i) => (
<ChatBubble key={i} socketId={props.socket.id} message={message} />
))}
</div>
)
}
.chatBox {
width: 100%;
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
padding: 8px;
}
App Component
import {useState} from 'react';
import useWebSocket from '../hooks/useWebSocket';
import * as styles from '../styles/App.module.css'
import ChatInput from './ChatInput';
import ChatBox from './ChatBox';
/**
* The main container for the application. Displays
* a title, the box housing the messages, and the input
* element for sending messages.
* @returns
*/
export default function App() {
const [messages, setMessages] = useState([]);
const socket = useWebSocket(
process.env.FLASK_URL, (data) => {setMessages(prevMessages => [...prevMessages, data])});
// Loop through the users and display them
return (
<div className={styles.app}>
<h1>WittCepter Chat</h1>
<ChatBox socket={socket} messages={messages} />
<ChatInput socket={socket} />
</div>
);
}
.app {
height: 100vh;
padding: 8px;
width: 100vw;
display: flex;
flex-direction: column;
align-items: center;
}
Running the Application
ERROR: Make sure to run the Flask server first so that our React app has a server to connect to!
pymon ./server.py
* Running on http://localhost:1235
npm start
> start
> webpack-dev-server --open --config webpack.config.cjs
<i> [webpack-dev-server] [HPM] Proxy created: /api -> http://localhost:1235
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:1234/, http://[::1]:1234/