Hooks
Complete reference for all melony hooks and their usage.
Overview
melony provides several hooks that give you fine-grained access to chat state and functionality. All hooks must be used within a MelonyProvider
.
useMelonyMessages
Returns grouped and processed messages with optional filtering and transformation.
Basic Usage
import { useMelonyMessages } from "melony";
function ChatMessages() {
const messages = useMelonyMessages();
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, i) => (
<div key={i}>{part.type === "text" && part.text}</div>
))}
</div>
))}
</div>
);
}
With Options
const messages = useMelonyMessages({
filter: (part) => part.type === "text",
joinTextDeltas: true,
limit: 50,
sortBy: (a, b) => a.createdAt.getTime() - b.createdAt.getTime(),
});
Options
filter
- Function to filter messages or partsgroupBy
- Function to group messagessortBy
- Function to sort messageslimit
- Maximum number of messages to keepjoinTextDeltas
- Whether to join text deltas automaticallyuseMelonySend
Returns a function to send new messages to the chat.
Basic Usage
import { useMelonySend } from "melony";
function ChatInput() {
const send = useMelonySend();
const handleSend = () => {
send("Hello, world!");
};
return (
<button onClick={handleSend}>
Send Message
</button>
);
}
With Error Handling
function ChatInput() {
const send = useMelonySend();
const handleSend = async (message: string) => {
try {
await send(message);
} catch (error) {
console.error("Failed to send message:", error);
}
};
return (
<input
onKeyPress={(e) => {
if (e.key === "Enter") {
handleSend(e.currentTarget.value);
}
}}
/>
);
}
Return Value
Type: (message: string) => Promise<void>
Function that sends a message to the chat and returns a Promise that resolves when the message is sent.
useMelonyStatus
Returns the current conversation state.
Basic Usage
import { useMelonyStatus } from "melony";
function ChatStatus() {
const status = useMelonyStatus();
return (
<div>
Status: {status}
{status === "streaming" && <div>Assistant is typing...</div>}
{status === "error" && <div>Something went wrong</div>}
</div>
);
}
Status Values
"idle"
- No active requests"requested"
- Request sent, waiting for response"streaming"
- Receiving streaming response"error"
- An error occurreduseMelonyPart
Subscribe to individual parts as they stream in for real-time updates.
Basic Usage
import { useMelonyPart } from "melony";
function PartListener() {
useMelonyPart((part) => {
console.log("New part received:", part);
});
return <div>Listening for parts...</div>;
}
With Typing Indicator
import { useState } from "react";
import { useMelonyPart } from "melony";
function TypingIndicator() {
const [isTyping, setIsTyping] = useState(false);
useMelonyPart((part) => {
if (part.type === "text" && part.text) {
setIsTyping(true);
setTimeout(() => setIsTyping(false), 1000);
}
});
return (
<div>
{isTyping && <div>Assistant is typing...</div>}
</div>
);
}
Callback Function
Type: (part: MelonyPart) => void
Function called for each part as it streams in. Use this for real-time updates, typing indicators, or custom processing.
Hook Combinations
Combine multiple hooks to build sophisticated chat interfaces:
Complete Chat Interface
import {
useMelonyMessages,
useMelonySend,
useMelonyStatus,
useMelonyPart
} from "melony";
function CompleteChat() {
const messages = useMelonyMessages();
const send = useMelonySend();
const status = useMelonyStatus();
const [typing, setTyping] = useState(false);
useMelonyPart((part) => {
if (part.type === "text") {
setTyping(true);
setTimeout(() => setTyping(false), 1000);
}
});
return (
<div className="chat">
<div className="messages">
{messages.map((message) => (
<div key={message.id} className={`message ${message.role}`}>
{message.parts.map((part, i) => (
<div key={i}>{part.type === "text" && part.text}</div>
))}
</div>
))}
{typing && <div>Assistant is typing...</div>}
</div>
<div className="input">
<button
onClick={() => send("Hello!")}
disabled={status === "streaming"}
>
{status === "streaming" ? "Sending..." : "Send"}
</button>
{status === "error" && <div>Error occurred</div>}
</div>
</div>
);
}
Filtered Messages with Status
function FilteredChat() {
const textMessages = useMelonyMessages({
filter: (part) => part.type === "text",
limit: 20,
});
const status = useMelonyStatus();
const send = useMelonySend();
return (
<div>
<div className="status">
Status: {status} | Messages: {textMessages.length}
</div>
{textMessages.map((message) => (
<div key={message.id}>
{message.parts.map((part, i) => (
<p key={i}>{part.text}</p>
))}
</div>
))}
<button
onClick={() => send("New message")}
disabled={status === "streaming"}
>
Send
</button>
</div>
);
}
Custom Hooks
Create your own custom hooks that combine melony functionality:
import { useMelonyMessages, useMelonySend, useMelonyStatus } from "melony";
// Custom hook for chat statistics
function useChatStats() {
const messages = useMelonyMessages();
return useMemo(() => {
const userCount = messages.filter(m => m.role === "user").length;
const assistantCount = messages.filter(m => m.role === "assistant").length;
const totalWords = messages.reduce((count, message) =>
count + message.parts.reduce((partCount, part) =>
partCount + (part.text?.split(' ').length || 0), 0
), 0
);
return { userCount, assistantCount, totalWords };
}, [messages]);
}
// Custom hook for message search
function useMessageSearch(query: string) {
const messages = useMelonyMessages();
return useMemo(() => {
if (!query) return messages;
return messages.filter(message =>
message.parts.some(part =>
part.text?.toLowerCase().includes(query.toLowerCase())
)
);
}, [messages, query]);
}
// Custom hook for chat controls
function useChatControls() {
const send = useMelonySend();
const status = useMelonyStatus();
const sendWithRetry = useCallback(async (message: string, maxRetries = 3) => {
let attempts = 0;
while (attempts < maxRetries) {
try {
await send(message);
break;
} catch (error) {
attempts++;
if (attempts >= maxRetries) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * attempts));
}
}
}, [send]);
return {
send,
sendWithRetry,
status,
isIdle: status === "idle",
isStreaming: status === "streaming",
hasError: status === "error",
};
}
// Usage
function MyChat() {
const stats = useChatStats();
const searchResults = useMessageSearch("hello");
const { send, isStreaming, hasError } = useChatControls();
return (
<div>
<div>Stats: {stats.userCount} user, {stats.assistantCount} assistant</div>
<div>Search results: {searchResults.length}</div>
<button onClick={() => send("Hello")} disabled={isStreaming}>
Send
</button>
{hasError && <div>Error occurred</div>}
</div>
);
}
Performance Tips
Use useMemo for expensive operations
Wrap expensive computations in useMemo to avoid recalculating on every render.
Use useCallback for event handlers
Wrap event handlers in useCallback to prevent unnecessary re-renders of child components.
Limit message count
Use the limit option in useMelonyMessages to prevent memory issues with large conversations.
Filter early
Use the filter option in useMelonyMessages instead of filtering in your render function.
Troubleshooting
Hooks not working
Make sure you're using hooks inside a MelonyProvider component.
Messages not updating
Check that your server is sending the correct format and that the endpoint is accessible.
Performance issues
Use the limit option and consider implementing virtual scrolling for large message lists.