← Back to blog
INTEGRATION6 min read

Add Realtime to a React App with NoLag

HB
Henco Burger
July 28, 2026

React apps built with Vite, Create React App, or any other bundler can add realtime with a small custom hook. The hook owns the connection lifecycle so your components just render messages and call a publish function. This works in any React setup; if you are on Next.js, see the Next.js guide for the extra server-token step.

1. Install the SDK

npm install @nolag/js-sdk

2. A reusable hook

import { useEffect, useRef, useState, useCallback } from 'react'
import { NoLag } from '@nolag/js-sdk'
import type { RoomContext } from '@nolag/js-sdk'

type Message = { text: string }

export function useNoLagRoom(token: string, app: string, roomName: string, topic: string) {
  const [messages, setMessages] = useState([] as Message[])
  const roomRef = useRef(null as RoomContext | null)

  useEffect(() => {
    let client: ReturnType<typeof NoLag> | undefined
    let active = true

    ;(async () => {
      client = NoLag(token)
      await client.connect()
      if (!active) return

      const room = client.setApp(app).setRoom(roomName)
      room.subscribe(topic)
      room.on<Message>(topic, (data) => setMessages((m) => [...m, data]))
      roomRef.current = room
    })()

    return () => {
      active = false
      roomRef.current = null
      client?.disconnect()
    }
  }, [token, app, roomName, topic])

  const publish = useCallback(
    (data: Message) => {
      roomRef.current?.emit(topic, data)
      // Publishers never receive their own messages, so append it locally
      setMessages((m) => [...m, data])
    },
    [topic],
  )

  return { messages, publish }
}

The publish function appends the message to local state as well as emitting it. NoLag never delivers a message back to the actor that published it, so without that line the sender's own text would never show up in their list.

3. Use it in a component

// Create the app and its `general` room first. The slug you get back has a
// random suffix (for example `chat-a3f9`), and that suffixed slug is the app
// name to pass.
const APP_SLUG = 'chat-a3f9'

function Chat({ token }: { token: string }) {
  const { messages, publish } = useNoLagRoom(token, APP_SLUG, 'general', 'message')

  return (
    <div>
      <ul>
        {messages.map((m, i) => (
          <li key={i}>{m.text}</li>
        ))}
      </ul>
      <button onClick={() => publish({ text: 'Hello!' })}>Send</button>
    </div>
  )
}

Keep the token off the client

Fetch the token from your own backend rather than hardcoding a project key in the bundle. Any server can mint one by calling the NoLag REST API, and for browser sessions the production pattern is a short-lived client token scoped to what that user may do.

Why the cleanup matters

The hook's cleanup function runs on unmount and when the dependencies change. Clearing the room ref and disconnecting prevents duplicate subscriptions and leaked WebSocket connections as users navigate around your app.

Next steps