---
title: "Add Realtime to a React App with NoLag"
description: How to add realtime messaging to any React app with NoLag using a small reusable hook that handles connect, subscribe, and cleanup.
excerpt: "A single useNoLagRoom hook is all most React apps need. Here is one that connects, subscribes, publishes, and tears down cleanly on unmount."
date: '2026-07-28'
readTime: 6 min read
category: Integration
---

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](/blog/add-realtime-to-nextjs) for the extra server-token step.

## 1. Install the SDK

```bash
npm install @nolag/js-sdk
```

## 2. A reusable hook

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

export function useNoLagRoom(token: string, app: string, roomName: string, topic: string) {
  const [messages, setMessages] = useState<any[]>([])
  const roomRef = useRef<any>(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(topic, (data: any) => setMessages((m) => [...m, data]))
      roomRef.current = room
    })()

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

  const publish = useCallback(
    (data: unknown) => roomRef.current?.emit(topic, data),
    [topic],
  )

  return { messages, publish }
}
```

## 3. Use it in a component

```tsx
function Chat({ token }: { token: string }) {
  const { messages, publish } = useNoLagRoom(token, 'chat', '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](/docs/agents) 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

- Walk through the full flow in the [5-minute quick start](/docs/getting-started).
- Use a [blueprint SDK](/docs/high-level-sdks) if you would rather not wire chat, notifications, or dashboards by hand.
