DEV Community

tom-takeru
tom-takeru

Posted on

Real-Time Web Application demo with WebSocket - Frontend

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
Enter fullscreen mode Exit fullscreen mode

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 as useMessages, 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Key Functionalities

  1. State Management: Manages connection status, current message, and sending state.
  2. WebSocket Lifecycle: Handles connection setup, events (onopen, onmessage, onerror, onclose), and teardown.
  3. 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.


Links to the Series

Top comments (0)