Melony
HomeGitHub

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 parts
groupBy - Function to group messages
sortBy - Function to sort messages
limit - Maximum number of messages to keep
joinTextDeltas - Whether to join text deltas automatically

useMelonySend

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 occurred

useMelonyPart

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.