EdgeStream
useMessage (Custom Hook Pattern)
EdgeStream's React bindings provide useSubscription as the core hook. The useMessage and useMessageHistory patterns are built on top of it — you implement them using useSubscription + useState. This page shows the complete pattern.
useMessage Pattern — Latest Message Only
Build a hook that returns the latest message for a topic, causing a re-render on each new arrival:
import { useState, useCallback } from 'react';
import { useSubscription } from 'edge-stream-js-react';
import type { IEnvelope } from 'edge-stream-js';
export function useMessage<T = unknown>(serverId: string, topic: string) {
const [message, setMessage] = useState<IEnvelope<T> | null>(null);
const [receivedAt, setReceivedAt] = useState<Date | null>(null);
const handler = useCallback((envelope: IEnvelope<T>) => {
setMessage(envelope);
setReceivedAt(new Date());
}, []);
const { isSubscribed, error } = useSubscription(serverId, topic, handler);
return { message, receivedAt, isSubscribed, error };
}
Using useMessage in a Component
function WorkflowStatusBadge({ workflowId }: { workflowId: string }) {
const { message, receivedAt } = useMessage<{ status: string; progress: number }>(
'bas',
`workflow.${workflowId}.status`
);
if (!message) return <span>Waiting...</span>;
return (
<div>
<span className={`badge badge-${message.body.status}`}>
{message.body.status}
</span>
<span>{message.body.progress}%</span>
<small>Updated: {receivedAt?.toLocaleTimeString()}</small>
</div>
);
}
useMessageHistory Pattern — N Most Recent Messages
import { useState, useCallback } from 'react';
import { useSubscription } from 'edge-stream-js-react';
import type { IEnvelope } from 'edge-stream-js';
export function useMessageHistory<T = unknown>(
serverId: string,
topic: string,
maxCount = 50
) {
const [messages, setMessages] = useState<IEnvelope<T>[]>([]);
const handler = useCallback((envelope: IEnvelope<T>) => {
setMessages(prev => {
const updated = [envelope, ...prev];
return updated.slice(0, maxCount); // keep only most recent N
});
}, [maxCount]);
const { isSubscribed, error } = useSubscription(serverId, topic, handler);
return { messages, isSubscribed, error };
}
Using useMessageHistory for a Live Log Feed
function AuditLogFeed() {
const { messages, isSubscribed } = useMessageHistory<{
action: string;
userId: string;
timestamp: string;
}>('bas', 'audit.*', 100);
return (
<div>
<h3>Audit Log {isSubscribed ? '' : '(connecting...)'}</h3>
<ul style={{ maxHeight: '400px', overflow: 'auto' }}>
{messages.map(msg => (
<li key={msg.meta.id}>
<strong>{msg.body.action}</strong> by {msg.body.userId}
<small>{msg.body.timestamp}</small>
</li>
))}
</ul>
</div>
);
}
Filtering in the Handler
// Only update state for messages that matter to this component
function UserTaskList({ userId }: { userId: string }) {
const [tasks, setTasks] = useState([]);
const handler = useCallback((envelope) => {
// Filter inside the handler — no unnecessary re-renders for other users
if (envelope.body.assignedTo === userId) {
setTasks(prev => [...prev, envelope.body]);
}
}, [userId]);
useSubscription('bas', 'workflow.task.*', handler);
return <TaskList tasks={tasks} />;
}
Re-render Budget
Each call to
setMessage or setMessages triggers a React re-render. For high-frequency topics (e.g., agent token streaming at 60 messages/second), consider batching state updates with a useRef accumulator and only calling setState on a throttled timer to stay within React's re-render budget.