Features
Chat Rooms
The template supports both private (1-on-1) and group chat rooms. This section explains how rooms are created, managed, and displayed in the UI.
Room Types
Private Rooms
One-on-one conversations between two users. The template automatically finds existing private rooms or creates new ones when needed.
- Always has exactly 2 participants
- No group name or avatar
- Created automatically when starting a chat
- Display shows the other user's name and avatar
Group Rooms
Multi-user conversations that can have a custom name and avatar. Created explicitly by users.
- Can have 2+ participants
- Optional group name and avatar
- Created via the "Create Group" button
- Shows group avatar or initials of participants
Creating Rooms
Private Room Creation
When a user wants to start a private chat, the template uses ensureCreatePrivateChat from room.service.ts:
- Searches for an existing private room between the two users using
getPrivateChatByUserIds - If found, returns the existing room ID (prevents duplicate rooms)
- If not found, creates a new room with
createPrivateChat, initializing:type: "private"participants: [uid1, uid2]unreadCounts: { [uid1]: 0, [uid2]: 0 }
Group Room Creation
Group rooms are created via createGroupChat which:
- Accepts creator UID, participant UIDs, optional group name and avatar URL
- Ensures the creator is included in participants (deduplicates array)
- Initializes unread counts: 0 for creator, 1 for others (they haven't seen the creation message)
- Creates a system message: "created the group" as the initial lastMessage
- Sets
type: "group"and optional group metadata
Room List Display
Querying User's Rooms
The room list sidebar uses subscribeRoomsByUser from rooms.service.ts to show all rooms where the current user is a participant:
query(
collection(db, "rooms"),
where("participants", "array-contains", currentUid),
orderBy("lastMessage.createdAt", "desc")
)This query requires a Firestore composite index on participants and lastMessage.createdAt. Firebase will prompt you to create it on first use.
Room List Item Rendering
Each room in the list is rendered by ChatRoomItem which conditionally renders:
- Private rooms:
PrivateRoomItem– shows the other user's avatar and name - Group rooms:
GroupRoomItem– shows group avatar (or initials) and group name
Both show the last message preview, unread count badge, and timestamp. The active room is highlighted based on state from chat.store.ts.
Room Data Structure
Every room document in Firestore has this structure:
rooms/{roomId} {
type: "private" | "group";
groupName?: string | null; // only for groups
groupAvatarURL?: string | null; // only for groups
participants: string[]; // array of UIDs
participantsCount: number; // denormalized count
unreadCounts: { // per-user counters
[uid: string]: number;
};
createdBy: string; // UID of creator
createdAt: Timestamp;
lastMessage: { // denormalized preview
text: string;
senderId: string;
createdAt: Timestamp;
type: "text" | "system";
} | null;
}See features/chat/types/room.types.ts for the TypeScript definitions.
Implementation Hooks
The template provides these hooks for working with rooms:
useRoomList()– subscribes to all rooms for the current user, returns{ rooms, isLoading, error }useRoom(roomId)– subscribes to a specific room document, returns{ room, isLoading, error }useParticipants(roomId)– fetches user info for all participants in a room