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
| Parameter | Type | Required | Description |
|---|
topic | string | Yes | The channel/topic name to subscribe to |
callback | function | Yes | Callback function called when messages are received |
Callback signature: (message, metadata) => void
| Parameter | Type | Description |
|---|
message | any | The message data received from the channel |
metadata | object | Message metadata |
metadata.offset | number | Message sequence number |
metadata.timestamp | string | ISO timestamp when message was sent |
metadata.replay | boolean | True if this is a replayed (missed) message |
metadata.channel | string | Channel name |
Returns
| Property | Type | Description |
|---|
streaming | boolean | Whether currently subscribed and streaming |
subscribe | function | (Optional) Function to manually subscribe - rarely needed since subscription is automatic |
unsubscribe | function | (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
| Parameter | Type | Required | Description |
|---|
topic | string | Yes | The channel/topic name to subscribe to |
Returns
| Property | Type | Description |
|---|
message | any | The latest message received |
metadata | object | Message metadata |
streaming | boolean | Whether currently subscribed |
subscribe | function | Function to start subscription |
unsubscribe | function | Function 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
| Parameter | Type | Required | Description |
|---|
topic | string | Yes | The channel/topic name to publish to |
Returns
Returns a publish function that accepts any serializable data.
publish(data)
| Parameter | Type | Description |
|---|
data | any | Any 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
| Property | Type | Description |
|---|
status | string | Current connection status |
Connection States
| State | Description |
|---|
disconnected | Not connected |
connecting | Connecting to server |
authenticating | Authenticating with API key |
connected | Connected and ready |
reconnecting | Reconnecting after connection loss |
unauthorized | Invalid API key |
failed | Connection 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