Portal Community

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.