Skip to main content

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

ParameterTypeRequiredDescription
conversationIdstring | nullYesThe 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

PropertyTypeDescription
conversationConversationRow | nullThe conversation record with joined user_a and user_b profile data.
messagesMessageRow[]Array of messages in chronological order (oldest first). Each includes sender profile and optional reply_to data.
participantsParticipantRow[]Active participants with user profile data.
loadingbooleantrue during initial message fetch.
loadingMorebooleantrue while loading older messages (pagination).
sendingbooleantrue while a message is being sent.
hasMorebooleantrue if there are older messages to load.
errorstring | nullError message from the most recent failed operation.
userUser | nullThe current authenticated user.
sendMessage(content, type?, replyToId?) => PromiseSend 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) => PromiseEdit an existing message. Preserves original_content and sets edited_at. Only the sender can edit their own messages.
deleteMessage(messageId) => PromiseSoft-delete a message (sets deleted_at). Only the sender can delete. Removes from local state.
loadMore() => voidLoad 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) => PromiseChange the conversation state. Valid states: "open", "paused", "resting", "closed".
updatePresenceNote(note) => PromiseSet or clear the current user's presence note in the conversation.
togglePinned() => PromiseToggle 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

ParameterTypeRequiredDescription
conversationIdstringYesThe conversation ID to manage participants for.

Return Value

PropertyTypeDescription
loadingbooleantrue while any operation is in progress.
addParticipant(userId) => PromiseAdd a user to the conversation with role "member".
removeParticipant(userId) => PromiseRemove a user from the conversation (sets status to "removed").
leaveConversation() => PromiseLeave the conversation (sets current user's status to "left").
updateRole(userId, newRole) => PromiseUpdate 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