Introduction
In this article, I will explore the frontend implementation of our real-time WebSocket application. Built with Next.js and TypeScript, the frontend serves as an interactive interface for sending and receiving real-time messages. Let's dive into the details of how this application is structured and how the components interact to provide a seamless WebSocket experience.
Project Structure
https://github.com/tom-takeru/web-socket-demo
The frontend project is organized to ensure modularity and reusability. Below is the updated directory structure:
./frontend/src
├── app
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── HomePage.tsx
│ ├── message
│ │ ├── MessageInputForm.tsx
│ │ ├── MessageList.tsx
│ │ └── MessageSection.tsx
│ └── websocket
│ ├── WebSocketControls.tsx
│ ├── WebSocketSection.tsx
│ └── WebSocketStatus.tsx
└── hooks
└── useMessages.ts
Key Directories and Files
-
app/
: Contains global styles and layout definitions for the application. -
components/
: Includes the primary components for messages and WebSocket handling. -
hooks/
: Contains custom hooks, such asuseMessages
, for managing state and logic. -
HomePage.tsx
: The central page that ties everything together.
Core Component: HomePage.tsx
HomePage.tsx
is the main entry point for the WebSocket application. It integrates message and WebSocket-related components while managing the application's state and lifecycle.
Code Walkthrough
"use client";
import { useState, useRef, useEffect } from "react";
import { useMessages } from "@/hooks/useMessages";
import MessageSection from "@/components/message/MessageSection";
import WebSocketSection from "@/components/websocket/WebSocketSection";
export default function HomePage() {
const { messages, clearMessages, addMessage } = useMessages();
const [connectionStatus, setConnectionStatus] = useState<
"Connected" | "Disconnected"
>("Disconnected");
const [isSending, setIsSending] = useState<boolean>(false);
const [message, setMessage] = useState<string>("");
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, []);
const handleOpen = () => {
console.log("Connected to WebSocket");
setConnectionStatus("Connected");
};
const handleMessage = (event: MessageEvent) => {
console.log("Message from server:", event.data);
const data = JSON.parse(event.data);
addMessage(data.message, data.timestamp);
setMessage("");
setIsSending(false);
};
const handleError = (error: Event) => {
console.log("WebSocket error:", error);
alert("Failed to connect to WebSocket.");
};
const handleClose = () => {
console.log("WebSocket closed");
setConnectionStatus("Disconnected");
wsRef.current = null;
};
const startWebSocket = () => {
if (wsRef.current && wsRef.current.readyState !== WebSocket.CLOSED) {
alert("WebSocket is already open");
return;
}
clearMessages();
const ws = new WebSocket("ws://localhost:8080/ws");
wsRef.current = ws;
ws.onopen = handleOpen;
ws.onmessage = handleMessage;
ws.onerror = handleError;
ws.onclose = handleClose;
};
const stopWebSocket = () => {
if (!wsRef.current) {
alert("WebSocket is not open");
return;
}
wsRef.current.close();
wsRef.current = null;
setConnectionStatus("Disconnected");
};
const sendMessage = async () => {
if (message.trim() === "") {
alert("Message cannot be empty");
return;
}
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
alert("WebSocket is not open");
return;
}
setIsSending(true);
const jsonMessage = JSON.stringify({ message });
wsRef.current.send(jsonMessage);
setMessage("");
};
return (
<div className="flex flex-col items-center justify-center min-h-screen p-8 pb-20 sm:p-20 font-sans">
<h1 className="text-3xl font-bold">WebSocket Demo App</h1>
<main className="flex flex-col gap-2 items-center w-full max-w-md">
<MessageSection
messages={messages}
message={message}
setMessage={setMessage}
sendMessage={sendMessage}
isDisabled={connectionStatus === "Disconnected" || isSending}
/>
<WebSocketSection
connectionStatus={connectionStatus}
startWebSocket={startWebSocket}
stopWebSocket={stopWebSocket}
/>
</main>
</div>
);
}
Key Functionalities
- State Management: Manages connection status, current message, and sending state.
-
WebSocket Lifecycle: Handles connection setup, events (
onopen
,onmessage
,onerror
,onclose
), and teardown. - User Interaction: Provides clear feedback and controls for starting/stopping the connection and sending messages.
Conclusion
The HomePage.tsx
component demonstrates the integration of WebSocket functionalities with a clean React structure. Its focus on state management and user interaction makes it the backbone of the application's frontend.
In the next article, I will explore the backend implementation using Gin and Go, detailing how the WebSocket server handles connections and messages.
Top comments (0)