refactor(frontend): Phase 2 - extract business logic into custom hooks
Separate concerns - move Socket.IO and form logic from components to reusable hooks New Hooks: - useForm: Generic form state management with handleChange/handleSubmit/reset - useEventChat: Extract Socket.IO logic from EventChatPage (156 lines) * Manages messages, active users, connection state * Handles send message, load older messages with scroll preservation * Real-time updates via Socket.IO event listeners - useMatchChat: Extract Socket.IO logic from MatchChatPage (115 lines) * Manages 1:1 chat messages and connection * Loads message history from API * Real-time message sync via Socket.IO Pages Refactored: - EventChatPage: 661 → 564 lines (-97 lines, -15%) - MatchChatPage: 517 → 446 lines (-71 lines, -14%) Benefits: - Cleaner component code - UI separated from business logic - Reusable hooks can be used in other components - Easier to test - hooks can be unit tested independently - Better code organization - single responsibility principle - 168 lines eliminated from pages, moved to 271 lines of reusable hooks Phase 2 Total: -168 lines Grand Total (Phase 1+2): -389 lines (-12%)
This commit is contained in:
122
frontend/src/hooks/useMatchChat.js
Normal file
122
frontend/src/hooks/useMatchChat.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { connectSocket, getSocket } from '../services/socket';
|
||||
import { matchesAPI } from '../services/api';
|
||||
|
||||
/**
|
||||
* Custom hook for Match Chat functionality (1:1 private chat)
|
||||
* Extracts Socket.IO logic and chat state management from MatchChatPage
|
||||
*
|
||||
* @param {object} match - Match object with id and partner info
|
||||
* @param {number} userId - Current user ID
|
||||
* @param {string} slug - Match slug for API calls
|
||||
* @returns {object} Chat state and handlers
|
||||
*
|
||||
* @example
|
||||
* const {
|
||||
* messages,
|
||||
* isConnected,
|
||||
* sendMessage,
|
||||
* newMessage,
|
||||
* setNewMessage
|
||||
* } = useMatchChat(match, user.id, slug);
|
||||
*/
|
||||
const useMatchChat = (match, userId, slug) => {
|
||||
// Chat state
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [newMessage, setNewMessage] = useState('');
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
// Load message history from API
|
||||
useEffect(() => {
|
||||
const loadMessages = async () => {
|
||||
if (!match || !slug) return;
|
||||
|
||||
try {
|
||||
const result = await matchesAPI.getMatchMessages(slug);
|
||||
setMessages(result.data || []);
|
||||
} catch (error) {
|
||||
console.error('Failed to load messages:', error);
|
||||
}
|
||||
};
|
||||
loadMessages();
|
||||
}, [match, slug]);
|
||||
|
||||
// Socket.IO connection and event listeners
|
||||
useEffect(() => {
|
||||
// Wait for match to be loaded
|
||||
if (!match) return;
|
||||
|
||||
// Connect to Socket.IO
|
||||
const socket = connectSocket();
|
||||
|
||||
if (!socket) {
|
||||
console.error('Failed to connect to socket');
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper to join match room
|
||||
const joinMatchRoom = () => {
|
||||
setIsConnected(true);
|
||||
socket.emit('join_match_room', { matchId: match.id });
|
||||
console.log(`Joined match room ${match.id}`);
|
||||
};
|
||||
|
||||
// Socket event listeners
|
||||
socket.on('connect', joinMatchRoom);
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
setIsConnected(false);
|
||||
});
|
||||
|
||||
// Receive messages
|
||||
socket.on('match_message', (message) => {
|
||||
setMessages((prev) => [...prev, message]);
|
||||
});
|
||||
|
||||
// Join immediately if already connected
|
||||
if (socket.connected) {
|
||||
joinMatchRoom();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
socket.off('connect', joinMatchRoom);
|
||||
socket.off('disconnect');
|
||||
socket.off('match_message');
|
||||
};
|
||||
}, [match, userId]);
|
||||
|
||||
/**
|
||||
* Send a message to the match chat
|
||||
*/
|
||||
const sendMessage = (e) => {
|
||||
e.preventDefault();
|
||||
if (!newMessage.trim() || !match) return;
|
||||
|
||||
const socket = getSocket();
|
||||
if (!socket || !socket.connected) {
|
||||
alert('Not connected to chat server');
|
||||
return;
|
||||
}
|
||||
|
||||
// Send message via Socket.IO using numeric match ID
|
||||
socket.emit('send_match_message', {
|
||||
matchId: match.id,
|
||||
content: newMessage,
|
||||
});
|
||||
|
||||
setNewMessage('');
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
messages,
|
||||
newMessage,
|
||||
setNewMessage,
|
||||
isConnected,
|
||||
// Actions
|
||||
sendMessage
|
||||
};
|
||||
};
|
||||
|
||||
export default useMatchChat;
|
||||
Reference in New Issue
Block a user