Skip to main content

Installation

npm install @aptly-sdk/brook
Brook requires React 16.8.0 or later for hooks support.

Setup

Step 1: Initialize the Client

Create a Brook client instance outside your components:
import Brook from '@aptly-sdk/brook';

const client = new Brook({
  apiKey: 'your-api-key',
  verbose: false
});

Step 2: Add the Provider

Wrap your app with the BrookProvider to make the client available to all components:
import { BrookProvider } from '@aptly-sdk/brook/react';

function App() {
  return (
    <BrookProvider config={client}>
      <YourApp />
    </BrookProvider>
  );
}

Step 3: Use the Hooks

Now you can use Brook hooks in any component within the provider:
import { useState } from 'react';
import { useStream, usePublish } from '@aptly-sdk/brook/react';

function ChatComponent() {
  const [latestMessage, setLatestMessage] = useState(null);
  const publish = usePublish('chat-room');

  useStream('chat-room', (message, metadata) => {
    setLatestMessage(message);
  });

  const handleSend = () => {
    publish({ text: 'Hello!' });
  };

  return (
    <div>
      <p>Latest: {JSON.stringify(latestMessage)}</p>
      <button onClick={handleSend}>Send</button>
    </div>
  );
}

Hooks

useStream

Automatically subscribes to a topic and calls your callback function whenever a message is received.
useStream automatically subscribes on component mount and unsubscribes on unmount. You typically don’t need to use the subscribe and unsubscribe functions from the returned object - they’re only needed for manual control in advanced use cases.

Usage

import { useStream } from '@aptly-sdk/brook/react';

function MyComponent() {
  useStream('my-topic', (message, metadata) => {
    console.log('Received message:', message);
    console.log('Offset:', metadata.offset);
    console.log('Timestamp:', metadata.timestamp);
    console.log('Is replay:', metadata.replay);
  });

  return (
    <div>
      <p>Messages are being received automatically</p>
    </div>
  );
}
With streaming status:
function MyComponent() {
  const { streaming } = useStream('my-topic', (message, metadata) => {
    console.log('Received message:', message);
  });

  return (
    <div>
      <p>Status: {streaming ? 'Streaming' : 'Not streaming'}</p>
    </div>
  );
}

Parameters

ParameterTypeRequiredDescription
topicstringYesThe channel/topic name to subscribe to
callbackfunctionYesCallback function called when messages are received
Callback signature: (message, metadata) => void
ParameterTypeDescription
messageanyThe message data received from the channel
metadataobjectMessage metadata
metadata.offsetnumberMessage sequence number
metadata.timestampstringISO timestamp when message was sent
metadata.replaybooleanTrue if this is a replayed (missed) message
metadata.channelstringChannel name

Returns

PropertyTypeDescription
streamingbooleanWhether currently subscribed and streaming
subscribefunction(Optional) Function to manually subscribe - rarely needed since subscription is automatic
unsubscribefunction(Optional) Function to manually unsubscribe - rarely needed since cleanup is automatic

Example with State

function MessageDisplay() {
  const [messages, setMessages] = useState([]);

  const { streaming } = useStream('notifications', (message, metadata) => {
    setMessages(prev => [...prev, { message, metadata }]);
  });

  if (!streaming) return <p>Connecting...</p>;

  return (
    <div>
      <h3>Notifications</h3>
      {messages.map((msg, i) => (
        <div key={i}>
          <p>{msg.message?.text}</p>
          {msg.metadata?.replay && (
            <span className="badge">Missed Message</span>
          )}
          <small>Received at: {msg.metadata?.timestamp}</small>
        </div>
      ))}
    </div>
  );
}

useLazyStream

Subscribe to a topic on-demand (manual control over subscription).

Usage

import { useLazyStream } from '@aptly-sdk/brook/react';

function ManualSubscription() {
  const { message, metadata, streaming, subscribe, unsubscribe } = useLazyStream('my-topic');

  return (
    <div>
      <button onClick={subscribe} disabled={streaming}>
        Subscribe
      </button>
      <button onClick={unsubscribe} disabled={!streaming}>
        Unsubscribe
      </button>

      {streaming && (
        <div>
          <p>Status: Streaming</p>
          <p>Message: {JSON.stringify(message)}</p>
        </div>
      )}
    </div>
  );
}

Parameters

ParameterTypeRequiredDescription
topicstringYesThe channel/topic name to subscribe to

Returns

PropertyTypeDescription
messageanyThe latest message received
metadataobjectMessage metadata
streamingbooleanWhether currently subscribed
subscribefunctionFunction to start subscription
unsubscribefunctionFunction to stop subscription

Use Cases

  • Toggle Subscription
  • Conditional Subscription
function ToggleStream() {
  const { message, streaming, subscribe, unsubscribe } = useLazyStream('updates');

  const toggle = () => {
    if (streaming) {
      unsubscribe();
    } else {
      subscribe();
    }
  };

  return (
    <div>
      <button onClick={toggle}>
        {streaming ? 'Stop' : 'Start'} Streaming
      </button>
      {message && <p>{JSON.stringify(message)}</p>}
    </div>
  );
}

usePublish

Publish messages to a topic.

Usage

import { usePublish } from '@aptly-sdk/brook/react';

function PublishExample() {
  const publish = usePublish('my-topic');

  const handleClick = () => {
    publish({
      text: 'Hello World!',
      timestamp: Date.now()
    });
  };

  return <button onClick={handleClick}>Send Message</button>;
}

Parameters

ParameterTypeRequiredDescription
topicstringYesThe channel/topic name to publish to

Returns

Returns a publish function that accepts any serializable data. publish(data)
ParameterTypeDescription
dataanyAny JSON-serializable data to send

Examples

  • Simple Message
  • Form Submission
  • With Error Handling
function SendButton() {
  const publish = usePublish('chat');

  return (
    <button onClick={() => publish({ text: 'Hi!' })}>
      Send
    </button>
  );
}

useConnection

Monitor and control the connection status.

Usage

import { useConnection } from '@aptly-sdk/brook/react';

function ConnectionStatus() {
  const { status } = useConnection();

  return (
    <div className={`status status-${status}`}>
      Status: {status}
    </div>
  );
}

Returns

PropertyTypeDescription
statusstringCurrent connection status

Connection States

StateDescription
disconnectedNot connected
connectingConnecting to server
authenticatingAuthenticating with API key
connectedConnected and ready
reconnectingReconnecting after connection loss
unauthorizedInvalid API key
failedConnection failed

Examples

  • Status Indicator
  • Reconnection Handler
  • Conditional Rendering
function StatusIndicator() {
  const { status } = useConnection();

  const getColor = () => {
    switch(status) {
      case 'connected': return 'green';
      case 'connecting':
      case 'reconnecting': return 'yellow';
      default: return 'red';
    }
  };

  return (
    <div style={{ color: getColor() }}>
{status}
    </div>
  );
}

Complete Example

Here’s a complete chat application example:
import { useState } from 'react';
import Brook from '@aptly-sdk/brook';
import {
  BrookProvider,
  useStream,
  usePublish,
  useConnection
} from '@aptly-sdk/brook/react';

// Initialize client
const client = new Brook({ apiKey: 'your-api-key' });

function App() {
  return (
    <BrookProvider config={client}>
      <ChatApp />
    </BrookProvider>
  );
}

function ChatApp() {
  const { status } = useConnection();

  if (status !== 'connected') {
    return <div>Connecting to Brook... ({status})</div>;
  }

  return (
    <div>
      <ConnectionStatus />
      <MessageList />
      <MessageForm />
    </div>
  );
}

function ConnectionStatus() {
  const { status } = useConnection();

  return (
    <div className="status-bar">
      Connection: <span className={status}>{status}</span>
    </div>
  );
}

function MessageList() {
  const [messages, setMessages] = useState([]);

  useStream('chat-room', (message, metadata) => {
    setMessages(prev => [...prev, { message, metadata }]);
  });

  return (
    <div className="messages">
      {messages.map((msg, index) => (
        <div key={index} className="message">
          <p>{msg.message.text}</p>
          <small>
            {msg.metadata.replay && '[Replay] '}
            {new Date(msg.metadata.timestamp).toLocaleTimeString()}
          </small>
        </div>
      ))}
    </div>
  );
}

function MessageForm() {
  const [text, setText] = useState('');
  const publish = usePublish('chat-room');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!text.trim()) return;

    publish({
      text,
      user: 'anonymous',
      timestamp: Date.now()
    });

    setText('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Type a message..."
      />
      <button type="submit">Send</button>
    </form>
  );
}

export default App;

Best Practices

Use useStream for automatic subscription when you want to start receiving messages immediately on component mount.
Use useLazyStream for manual control when you need to conditionally subscribe based on user actions or state.
Always monitor connection status using useConnection to provide feedback when the connection is lost or unstable.
Message replay is automatic - when your app reconnects, you’ll receive any messages that were sent while offline.

Next Steps