Overview
The useJoinRoom hook manages the room joining flow, including:
- Form state management for room ID and username inputs
- Socket connection status monitoring
- Room joining with server communication
- Join credentials storage in session storage
- Automatic navigation to joined room
- Error handling and validation with Zod
- Analytics tracking with Umami
- Room ID formatting (uppercase, alphanumeric only)
This hook provides a complete solution for the join room page, handling all aspects from user input to server response.
Import
import { useJoinRoom } from '@/hooks/use-join-room';
Parameters
This hook takes no parameters.
Return Value
Returns an object with form state and handlers:
The current room ID input value (uppercase, max 6 characters)
Update the room ID input value
The current username input value
Update the username input value
Whether the room join request is in progress
Validation or server error message to display
Whether the socket is connected to the server
Whether the socket has finished initialization
handleJoinRoom
(e: React.FormEvent) => Promise<void>
Form submit handler that validates inputs and joins the roomParameters:
e: Form event (automatically prevents default)
handleRoomIdChange
(e: React.ChangeEvent<HTMLInputElement>) => void
Specialized input handler for room ID that automatically formats to uppercase and filters invalid charactersParameters:
Usage Example
'use client';
import { useJoinRoom } from '@/hooks/use-join-room';
export default function JoinRoomPage() {
const {
roomId,
setRoomId,
userName,
setUserName,
isLoading,
error,
isConnected,
isInitialized,
handleJoinRoom,
handleRoomIdChange,
} = useJoinRoom();
return (
<div className="join-room-page">
<h1>Join a Room</h1>
{!isInitialized && (
<div>Initializing...</div>
)}
{!isConnected && isInitialized && (
<div>Connecting to server...</div>
)}
<form onSubmit={handleJoinRoom}>
<div>
<label htmlFor="roomId">Room ID</label>
<input
id="roomId"
type="text"
value={roomId}
onChange={handleRoomIdChange}
placeholder="ABC123"
maxLength={6}
disabled={isLoading || !isConnected}
required
/>
<small>6 characters, letters and numbers only</small>
</div>
<div>
<label htmlFor="userName">Your Name</label>
<input
id="userName"
type="text"
value={userName}
onChange={(e) => setUserName(e.target.value)}
placeholder="Enter your name"
disabled={isLoading || !isConnected}
required
/>
</div>
{error && (
<div className="error">{error}</div>
)}
<button
type="submit"
disabled={isLoading || !isConnected || !roomId.trim() || !userName.trim()}
>
{isLoading ? 'Joining...' : 'Join Room'}
</button>
</form>
</div>
);
}
The hook uses Zod schema validation for both inputs:
Room ID Validation
// From @/types/schemas
const RoomIdSchema = z
.string()
.length(6, 'Room ID must be exactly 6 characters')
.regex(/^[A-Z0-9]+$/, 'Room ID can only contain uppercase letters and numbers');
Rules:
- Exact length: 6 characters
- Allowed characters: Uppercase letters (A-Z) and numbers (0-9)
- Auto-formatting:
handleRoomIdChange automatically converts to uppercase and filters invalid characters
Username Validation
// From @/types/schemas
const UserNameSchema = z
.string()
.min(2, 'Name must be at least 2 characters long')
.max(50, 'Name must be 50 characters or less')
.regex(
/^[a-zA-Z0-9\s\-_.!?]+$/,
'Name can only contain letters, numbers, spaces, and basic punctuation (- _ . ! ?)'
);
Rules:
- Minimum length: 2 characters
- Maximum length: 50 characters
- Allowed characters: Letters, numbers, spaces, and
- _ . ! ?
- Automatic trimming: Leading and trailing whitespace is removed
Validation Errors
Validation errors are displayed in the error field and include:
- “Room ID must be exactly 6 characters”
- “Room ID can only contain uppercase letters and numbers”
- “Name must be at least 2 characters long”
- “Name must be 50 characters or less”
- “Name can only contain letters, numbers, spaces, and basic punctuation (- _ . ! ?)”
- “Not connected to server. Please try again.” (connection error)
- Server-specific errors (e.g., “Room not found”, “Room is full”)
The handleRoomIdChange function provides automatic formatting:
const handleRoomIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
.toUpperCase() // Convert to uppercase
.replace(/[^A-Z0-9]/g, ''); // Remove invalid characters
if (value.length <= 6) { // Enforce max length
setRoomId(value);
}
};
Benefits:
- Users can type lowercase letters (automatically converted)
- Invalid characters are silently filtered
- Length is enforced at input level
- Provides better UX than validation errors
Room Joining Flow
When handleJoinRoom is called:
const handleJoinRoom = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
// Validate room ID
const roomIdResult = RoomIdSchema.safeParse(roomId.trim().toUpperCase());
if (!roomIdResult.success) {
setError(roomIdResult.error.issues[0].message);
return;
}
// Validate username
const userNameResult = UserNameSchema.safeParse(userName.trim());
if (!userNameResult.success) {
setError(userNameResult.error.issues[0].message);
return;
}
const joinData = {
roomId: roomIdResult.data,
userName: userNameResult.data,
};
// Check socket connection
if (!socket || !isConnected) {
setError('Not connected to server. Please try again.');
return;
}
setIsLoading(true);
// Listen for room join response
socket.once('room-joined', () => {
// Store join credentials
roomSessionStorage.setJoinData({
roomId: joinData.roomId,
userName: joinData.userName,
});
// Navigate to room
router.push(`/room/${joinData.roomId}`);
});
// Emit join room event
socket.emit('join-room', joinData);
};
2. Server Response
The server responds with one of:
- Success:
room-joined event with { room: Room, user: User }
- Error:
room-error event with { error: string }
Common error messages:
- “Room not found”
- “Room is full”
- “Invalid room ID format”
- “Name is already taken in this room”
3. Session Storage
On success, the hook stores:
roomSessionStorage.setJoinData({
roomId: string, // 6-character room ID
userName: string, // Validated username
});
This allows useRoom to automatically join without prompting again.
4. Navigation
The hook navigates to /room/{roomId} where useRoom detects the stored credentials and joins automatically.
Analytics Tracking
The hook tracks these Umami events:
// On successful room join
trackUmamiEvent('room_joined', {
roomId: string,
});
// On room join error
trackUmamiEvent('room_join_error', {
roomId: string,
message: string,
});
Socket Events
Emitted:
socket.emit('join-room', {
roomId: string, // 6-character room ID (uppercase, alphanumeric)
userName: string, // Validated username (2-50 chars)
hostToken?: string, // Optional host token (for rejoining as host)
});
Listened to:
// Success response
socket.on('room-joined', (data: {
room: Room, // Complete room object
user: User, // Current user object
}) => {
// Handle success
});
// Error response
socket.on('room-error', (data: {
error: string, // Error message
}) => {
// Handle error
});
Type Definitions
interface UseJoinRoomReturn {
roomId: string;
setRoomId: (id: string) => void;
userName: string;
setUserName: (name: string) => void;
isLoading: boolean;
error: string;
isConnected: boolean;
isInitialized: boolean;
handleJoinRoom: (e: React.FormEvent) => Promise<void>;
handleRoomIdChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
interface JoinRoomData {
roomId: string;
userName: string;
hostToken?: string;
}
interface RoomJoinedResponse {
room: Room;
user: User;
}
interface ErrorResponse {
error: string;
}
Error Handling
The hook handles errors at multiple levels:
- Connection Errors: Checks socket connection before submission
- Validation Errors:
- Room ID validated separately with specific error message
- Username validated separately with specific error message
- First error is displayed to user
- Server Errors: Listens for
room-error event and displays error message
- Network Errors: Handles unexpected errors with generic message
Best Practices
- Use
handleRoomIdChange for room ID input for automatic formatting
- Disable form submission while loading or disconnected
- Show connection status to users (initializing, connecting, connected)
- Clear error messages on new submission attempts
- Provide real-time validation feedback in the UI
- Trim whitespace from inputs before validation
- Show character count or limits for better UX