Skip to content

Instantly share code, notes, and snippets.

@stereosteve
Last active April 21, 2025 23:08
Show Gist options
  • Save stereosteve/d11f90f209b5013139b09d1ea4cffbb4 to your computer and use it in GitHub Desktop.
Save stereosteve/d11f90f209b5013139b09d1ea4cffbb4 to your computer and use it in GitHub Desktop.
React hooks for using nats.ws

Some hooks to make it easy to use nats.ws in React:

// nats client:
const nc = useNats()

// simple subscription callback:
useNatsSubscription('chat', onMessage)
// A very basic chatroom example:
import { JSONCodec, Msg } from 'nats.ws'
import { FormEvent, useState } from 'react'
import './App.css'
import { useNats, useNatsSubscription } from './useNats'
const natsSubject = 'chat'
const sc = JSONCodec()
type ChatMsg = {
handle: string
msg: string
}
function App() {
const [handle, setHandle] = useState('')
const [msg, setMsg] = useState('')
const [log, setLog] = useState<ChatMsg[]>([])
const nc = useNats()
useNatsSubscription(natsSubject, onMessage)
async function onMessage(msg: Msg) {
const data = sc.decode(msg.data) as ChatMsg
setLog((old) => [...old, data])
}
async function sendMessage(e: FormEvent) {
e.preventDefault()
nc!.publish(natsSubject, sc.encode({ handle, msg }))
setMsg('')
}
return (
<div className="App">
<div className="chat-log">
{log.map((c, idx) => (
<div className="chat-msg" key={idx}>
<b>{c.handle}</b>: {c.msg}
</div>
))}
</div>
<hr />
<form onSubmit={sendMessage}>
<input
type="text"
value={handle}
onChange={(e) => setHandle(e.target.value)}
placeholder="handle"
required
/>
<input
type="text"
value={msg}
onChange={(e) => setMsg(e.target.value)}
placeholder="Say something..."
required
/>
<button>Send</button>
</form>
</div>
)
}
export default App
// a basic example of using SWR (https://swr.vercel.app/)
// to do request / reply to NATS
import { JSONCodec } from 'nats.ws'
import useSWR from 'swr'
import { natsClientPromise } from './useNats'
const sc = JSONCodec()
type TimeResponse = {
now: string
}
async function natsRequest(subj: string) {
console.time(`natsRequest:${subj}`)
const nc = await natsClientPromise
const resp = await nc.request(subj)
console.timeEnd(`natsRequest:${subj}`)
return resp
}
export function Clock() {
const { data, error } = useSWR('time', natsRequest, {
refreshInterval: 1000,
})
if (error) return <div>Error: {error.message}</div>
if (!data) return <div>loading...</div>
const t = sc.decode(data.data) as TimeResponse
const motd = data.headers?.get('motd')
return (
<div>
<b>server time: {t.now}</b>
{motd && <em>motd: {motd}</em>}
</div>
)
}
import { connect, headers, JSONCodec } from 'nats'
const sc = JSONCodec()
export async function clockServer() {
const nc = await connect({
servers: 'localhost:4222',
})
const sub = nc.subscribe('time')
console.log(`listening for ${sub.getSubject()} requests...`)
for await (const m of sub) {
const now = new Date().toISOString()
const h = headers()
h.append('motd', `gotta get down on Friday`)
m.respond(sc.encode({ now }), { headers: h })
}
}
import { connect, Msg, NatsConnection, NatsError } from 'nats.ws'
import { useState, useEffect } from 'react'
export const natsClientPromise = connect({ servers: 'ws://localhost:4242' })
export function useNats() {
const [nats, setNats] = useState<NatsConnection | null>(null)
useEffect(() => {
if (!nats) {
natsClientPromise
.then((nc) => {
setNats(nc)
})
.catch((err) => console.error('connect failed', err))
}
}, [])
return nats
}
type SuccessCallback = (msg: Msg) => Promise<void>
export function useNatsSubscription(subj: string, onMessage: SuccessCallback) {
const nc = useNats()
useEffect(() => {
if (!nc) return
const sub = nc.subscribe(subj, {
callback: function (err: NatsError | null, msg: Msg) {
if (err) {
console.error(err)
} else {
onMessage(msg)
}
},
})
return () => {
sub.unsubscribe()
}
}, [nc])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment