Adding Realtime capabilities using socket

In this tutorial, we'll learn how to implement real-time messaging capabilities using MagicJS.

Setting Up UI

  1. Create a new UI file named rt-message within the main-features folder and set its path to /.

  2. Add three buttons: Send Message, Leave Room, and Join Room.

  3. Import Space and Input components, and wrap the buttons and input field within the Space component.

  4. Add an input field and set the width of the input field to 300 for better usability.

  5. Create a div with a height of 100 to display the last message.

State Management

  1. Initialize two states to manage the last message and the input value.

  2. Incorporate these states within the UI, ensuring their proper rendering and functionality.

Refer the code below:

rt-message.tsx
import React, { useState } from "react"
import { Button, Input, Space } from "antd"

export default function Component(props: any) {
    const [msg, setMsg] = React.useState("")
    const [input, setInput] = React.useState("")

    return (
        <div>
            <div style={{ height: 100 }}>
                <p>Last Message:</p>
                <h1>{msg}</h1>
            </div>

            <Space>
                <Input
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    style={{ width: 300 }}
                />

                <Button>Send Message</Button>

                <Button>Leave Room</Button>

                <Button>Join Room</Button>
            </Space>
        </div>
    )
}

Backend Configuration

  1. Create a backend function named send-msg.

  2. Import io and emit a new message received from the frontend.

Refer the snippet below.

send-msg.server.tsx
import { createBackendFunction, io } from "@magicjs.dev/backend"

export default createBackendFunction(async function (msg: string) {
    io().emit('new-message', msg)

    return "Message from server"
})
  • The function accepts a parameter msg of type string.

  • Inside the function body:

    • It uses the io function to emit a message with the event name 'new-message' and the provided msg parameter.

Configure the frontend to call this function upon clicking the Send Message button, passing the input value and resetting it afterward.

Refer the snippet below.

<Button onClick={() => sendMsgServer(input).then(() => {
    setMsg(input);
    setInput('');
})}>
    Send Message
</Button>

Import the sendMsgServer from "./send-msg.server".

Real-Time Messaging

  1. Import { useSocket } from '@magicjs.dev/frontend';

  2. Utilize the useSocket() hook to establish socket connection.

  3. Implement a useEffect() hook to subscribe to incoming messages and update the state accordingly.

  4. Ensure proper subscription management by unsubscribing within the hook.

Refer the snippet below.

In rt-message.tsx
const socket = useSocket();

React.useEffect(() => {
    const unsub = socket.subscribe("new-message", (msg) => {
        setMsg(msg)
    })
    return unsub
})

Room Restriction

  1. Modify the backend to emit messages only to a specific room, e.g., "secret-room".

  2. Allow users to join and leave the room by invoking socket.joinRoom() and socket.leaveRoom() respectively in the frontend.

Refer the snippets below.

send-msg.server.tsx
import { createBackendFunction, io } from "@magicjs.dev/backend"

export default createBackendFunction(async function (msg: string) {
    io().to("secret-room").emit("new-message", msg)

    return "Message from server"
})
In rt-message.tsx
<Button onClick={() => socket.leaveRoom("secret-room")}>
    Leave Room
</Button>

<Button onClick={() => socket.joinRoom("secret-room")}>
    Join Room
</Button>
Expand for Tailwind styled code.
rt-message.tsx
import React from 'react';
import sendMsgServer from "./send-message.server";
import { useSocket } from '@magicjs.dev/frontend';

export default function Test() {
  const [msg, setMsg] = React.useState("")
  const [input, setInput] = React.useState("")
  const socket = useSocket();

  React.useEffect(() => {
    const unsub = socket.subscribe("new-message", (msg) => {
      setMsg(msg)
    })
    return unsub
  })

  return (
    <div className='flex flex-col justify-center items-center min-h-screen'>
      <div>
        <div className='text-[40px] text-[#212121] mb-10'>Latest Message:</div>
        <div className='h-[80px]'>
          <h1 className='text-[56px] font-medium text-[#147E1D]'>{msg}</h1>
        </div>
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          className='w-full border border-[#C1C1C1] rounded-[8px] mt-10'
        />
        <div className='flex flex-row gap-[33px] mt-10'>
          <button
            onClick={() => sendMsgServer(input).then(() => {
              setMsg(input);
              setInput('');
            })}
            className='text-[25px] text-[#151515] border border-[#BFBFBF] rounded-[10px] px-[62px] py-[15px] hover:opacity-70'
          >
            Send Message
          </button>
          <button
            onClick={() => socket.joinRoom("secret-room")}
            className='text-[25px] text-[#151515] border border-[#BFBFBF] rounded-[10px] px-[62px] py-[15px] hover:opacity-70'
          >
            Leave Room
          </button>
          <button
            onClick={() => socket.joinRoom("secret-room")}
            className='text-[25px] text-[#151515] border border-[#BFBFBF] rounded-[10px] px-[62px] py-[15px] hover:opacity-70'
          >
            Join Room
          </button>
        </div>
      </div>
    </div>
  )
}

Testing

  1. Open two browsers with the same page.

  2. Test sending messages within the room, ensuring they are received in real-time only by members of the room.

Last updated