useConversation
Location: src/hooks/useConversation.js
Overview
This file exports two hooks for conversation management:
useConversation-- the primary hook for a single conversation view, handling message fetching, sending, editing, deleting, real-time subscriptions, pagination, state management, and presence notes.useConversationParticipants-- manages participant membership in group conversations (add, remove, leave, update roles).
Messages are fetched in pages of 50, newest first (reversed for display with oldest at top). A real-time Supabase channel subscription listens for INSERT and UPDATE events on the messages table for the given conversation.
useConversation
Signature
function useConversation(
conversationId: string | null
): {
// State
conversation: ConversationRow | null,
messages: MessageRow[],
participants: ParticipantRow[],
loading: boolean,
loadingMore: boolean,
sending: boolean,
hasMore: boolean,
error: string | null,
user: User | null,
// Actions
sendMessage: (content: string, type?: string, replyToId?: string | null) => Promise<{ data?: MessageRow, error?: string }>,
editMessage: (messageId: string, newContent: string) => Promise<{ data?: MessageRow, error?: string }>,
deleteMessage: (messageId: string) => Promise<{ success?: boolean, error?: string }>,
loadMore: () => void,
markAsRead: () => Promise<void>,
updateState: (newState: "open" | "paused" | "resting" | "closed") => Promise<{ success?: boolean, error?: string }>,
updatePresenceNote: (note: string | null) => Promise<{ success?: boolean, error?: string }>,
togglePinned: () => Promise<{ success?: boolean, pinned?: boolean, error?: string }>,
refetch: () => Promise<void>,
refetchParticipants: () => Promise<void>
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
conversationId | string | null | Yes | The conversation ID to load. When this changes, messages, conversation details, and participants are re-fetched and the real-time subscription is re-established. |
Return Value
| Property | Type | Description |
|---|---|---|
conversation | ConversationRow | null | The conversation record with joined user_a and user_b profile data. |
messages | MessageRow[] | Array of messages in chronological order (oldest first). Each includes sender profile and optional reply_to data. |
participants | ParticipantRow[] | Active participants with user profile data. |
loading | boolean | true during initial message fetch. |
loadingMore | boolean | true while loading older messages (pagination). |
sending | boolean | true while a message is being sent. |
hasMore | boolean | true if there are older messages to load. |
error | string | null | Error message from the most recent failed operation. |
user | User | null | The current authenticated user. |
sendMessage | (content, type?, replyToId?) => Promise | Send a new message. Uses the send_message RPC function (SECURITY DEFINER). type defaults to "text". Returns { data } with the full message or { error }. |
editMessage | (messageId, newContent) => Promise | Edit an existing message. Preserves original_content and sets edited_at. Only the sender can edit their own messages. |
deleteMessage | (messageId) => Promise | Soft-delete a message (sets deleted_at). Only the sender can delete. Removes from local state. |
loadMore | () => void | Load the next page of older messages. No-op if hasMore is false or already loading. |
markAsRead | () => Promise<void> | Mark the conversation as read via the mark_conversation_read RPC function. Called automatically when messages are displayed. |
updateState | (newState) => Promise | Change the conversation state. Valid states: "open", "paused", "resting", "closed". |
updatePresenceNote | (note) => Promise | Set or clear the current user's presence note in the conversation. |
togglePinned | () => Promise | Toggle the pinned status for the current user's participant record. Returns { pinned } with the new value. |
refetch | () => Promise<void> | Re-fetch messages from the beginning. |
refetchParticipants | () => Promise<void> | Re-fetch participant data. |
Usage
function ConversationView({ conversationId }) {
const {
conversation, messages, loading, sending,
sendMessage, loadMore, hasMore
} = useConversation(conversationId);
const [input, setInput] = useState('');
const handleSend = async () => {
const { error } = await sendMessage(input);
if (!error) setInput('');
};
if (loading) return <Skeleton />;
return (
<div>
{hasMore && <button onClick={loadMore}>Load older messages</button>}
{messages.map(msg => (
<div key={msg.id}>
<strong>{msg.sender?.display_name}</strong>: {msg.content}
</div>
))}
<input value={input} onChange={e => setInput(e.target.value)} />
<button onClick={handleSend} disabled={sending}>Send</button>
</div>
);
}
useConversationParticipants
Signature
function useConversationParticipants(
conversationId: string
): {
loading: boolean,
addParticipant: (userId: string) => Promise<{ success?: boolean, error?: string }>,
removeParticipant: (userId: string) => Promise<{ success?: boolean, error?: string }>,
leaveConversation: () => Promise<{ success?: boolean, error?: string }>,
updateRole: (userId: string, newRole: string) => Promise<{ success?: boolean, error?: string }>
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
conversationId | string | Yes | The conversation ID to manage participants for. |
Return Value
| Property | Type | Description |
|---|---|---|
loading | boolean | true while any operation is in progress. |
addParticipant | (userId) => Promise | Add a user to the conversation with role "member". |
removeParticipant | (userId) => Promise | Remove a user from the conversation (sets status to "removed"). |
leaveConversation | () => Promise | Leave the conversation (sets current user's status to "left"). |
updateRole | (userId, newRole) => Promise | Update a participant's role. |
Usage
function GroupSettings({ conversationId }) {
const { addParticipant, removeParticipant, loading } = useConversationParticipants(conversationId);
return (
<div>
<button onClick={() => addParticipant(friendId)} disabled={loading}>
Add Friend
</button>
</div>
);
}
Last updated: 2026-02-07