Everything so far was setup. In this post, we finally connect the browser to the server and see a real message travel between them.
Step 1: Where the Socket Instance Lives
We already created src/lib/socket.ts back when we set up the client types. Here it is again, as a reminder of where our connection lives:
import { io, Socket } from "socket.io-client"; import type { ServerToClientEvents, ClientToServerEvents, } from "@/types/socket";
export const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io({ path: "/api/socket", });
Keeping this in its own file is important. If you called io() directly inside a component, React could create a new connection every time that component re-renders. By creating the socket once here and importing the same instance everywhere, every part of your app shares one single connection.
Step 2: Add a "hello" Event to Our Types
Open src/types/socket.ts and add one new event on each side, just for this test:
export interface ServerToClientEvents { message: (data: { text: string; sender: string }) => void; hello: (text: string) => void; }
export interface ClientToServerEvents { sendMessage: (data: { text: string; sender: string }) => void; sayHello: () => void; }
export interface InterServerEvents { ping: () => void; }
export interface SocketData { userId: string; }
Step 3: Handle These Events on the Server
Open server.ts, and add these two lines inside your existing io.on("connection", ...) block:
io.on("connection", (socket) => { console.log("A client connected:", socket.id);
socket.on("sayHello", () => { socket.emit("hello", "Hello from the server!"); });
socket.on("sendMessage", (data) => { console.log("Received:", data.text, "from", data.sender); });
socket.on("disconnect", () => { console.log("Client disconnected:", socket.id); }); });
When a client sends sayHello, the server immediately emits hello back to that exact same client, with a text message as the payload.
Step 4: Build a Simple Client Component
Replace the content of src/app/page.tsx with this:
"use client";
import { useEffect, useState } from "react"; import { socket } from "@/lib/socket";
export default function Home() { const [isConnected, setIsConnected] = useState(false); const [message, setMessage] = useState("");
useEffect(() => { function onConnect() { setIsConnected(true); }
function onDisconnect() { setIsConnected(false); }
function onHello(text: string) { setMessage(text); }
socket.on("connect", onConnect); socket.on("disconnect", onDisconnect); socket.on("hello", onHello);
return () => { socket.off("connect", onConnect); socket.off("disconnect", onDisconnect); socket.off("hello", onHello); }; }, []);
function handleSayHello() { socket.emit("sayHello"); }
return ( <main className="flex min-h-screen flex-col items-center justify-center gap-4 bg-gray-900 text-white"> <p> Status:{" "} <span className={isConnected ? "text-green-400" : "text-red-400"}> {isConnected ? "Connected" : "Disconnected"} </span> </p>
<button onClick={handleSayHello} className="rounded bg-blue-600 px-4 py-2 hover:bg-blue-700" > Say Hello to Server </button>
{message && <p className="text-gray-300">{message}</p>} </main> ); }
Step 5: Understanding This Code, Piece by Piece
"use client"is required at the top, since this component uses hooks and browser-only code, Server Components cannot do that- Inside
useEffect, we define named functions (onConnect,onDisconnect,onHello) instead of inline arrow functions, so we can properly remove them later socket.on(...)registers listeners when the component mounts- The
return () => {...}cleanup function removes those listeners when the component unmounts, usingsocket.off(...). This is important, without cleanup, listeners would pile up every time this component re-mounts - Clicking the button calls
socket.emit("sayHello"), which triggers the handler we wrote on the server - When the server responds with
hello, ouronHellolistener updates themessagestate, and React re-renders the text on screen
Step 6: Test It
Run your app:
npm run dev
Open http://localhost:3000. You should immediately see "Status: Connected" in green, since the socket connects automatically as soon as the page loads. Now click "Say Hello to Server". Within milliseconds, you should see "Hello from the server!" appear on the page.
Check your terminal too, you will see:
A client connected: <some-socket-id>
Step 7: Why Cleanup Matters
Try removing the return () => {...} cleanup block temporarily, and reload the page a few times using hot reload during development. You will notice onHello gets called multiple times for a single click, since old listeners never got removed. This is one of the most common bugs beginners hit with Socket.IO in React, so always pair socket.on with a matching socket.off in your cleanup function.
Summary
- We created a single shared socket instance in
src/lib/socket.ts, imported wherever needed - We added a simple
sayHello/helloevent pair between client and server - We built a component that shows connection status, sends an event on button click, and displays the server's response
- Cleanup with
socket.offinside theuseEffectreturn function is essential to avoid duplicate listeners
You now have a fully working real-time connection between Next.js and Socket.IO. In the next post, we go deeper into events, covering emit, on, and how to design good custom event names for a real application.
No comments:
Post a Comment