Frontend Custom Hooks - Complete Documentation¶
Purpose: Exhaustive documentation of all 5 custom React hooks
Repository:machineagents-fe/src/hooks/
Last Updated: 2025-12-29
Coverage: 100% (5/5 hooks)
Overview¶
The frontend uses 5 custom hooks to encapsulate reusable logic for HTTP requests, session management, prompt configuration, user features, and global video control.
1. useHttp Hook¶
File: src/hooks/useHttp.ts (75 lines)
Purpose: Generic HTTP client wrapper with automatic loading/error state management
Type Definitions¶
interface UseHttpOptions {
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
headers?: Record<string, string>;
body?: any;
}
interface UseHttpReturn<T> {
data: T | null;
loading: boolean;
error: string | null;
execute: (url: string, options?: UseHttpOptions) => Promise<T | null>;
reset: () => void;
}
Hook Signature¶
State Variables (3)¶
- data (
T | null) - Response data - loading (
boolean) - Request in progress - error (
string | null) - Error message if request failed
Methods (2)¶
1. execute(url, options)¶
Purpose: Executes HTTP request with automatic state management
Parameters:
url(string) - Full URL to requestoptions(UseHttpOptions, optional) - Request configuration
Returns: Promise<T | null> - Parsed JSON response or null on error
Features:
- ✅ Automatic JSON parsing
- ✅ FormData support (auto-removes Content-Type header)
- ✅ Error handling with descriptive messages
- ✅ Loading state management
- ✅ Generic type support
Implementation:
const execute = useCallback(
async (url: string, options: UseHttpOptions = {}): Promise<T | null> => {
setLoading(true);
setError(null);
try {
const {
method = "GET",
headers = { "Content-Type": "application/json" },
body,
} = options;
const config: RequestInit = {
method,
headers,
};
if (body) {
if (body instanceof FormData) {
// Remove Content-Type header for FormData to let browser set boundary
delete config.headers?.["Content-Type"];
config.body = body;
} else {
config.body = JSON.stringify(body);
}
}
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(
`HTTP Error: ${response.status} ${response.statusText}`
);
}
const result = await response.json();
setData(result);
return result;
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : "An error occurred";
setError(errorMessage);
return null;
} finally {
setLoading(false);
}
},
[]
);
2. reset()¶
Purpose: Clears all state (data, error, loading)
Implementation:
Usage Examples¶
Basic GET Request:
const { data, loading, error, execute } = useHttp<User>();
useEffect(() => {
execute("https://api.example.com/user/123");
}, []);
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return <UserProfile user={data} />;
POST Request with JSON:
const { execute, loading } = useHttp();
const handleSubmit = async (formData: FormData) => {
const result = await execute("https://api.example.com/chatbots/create", {
method: "POST",
body: {
name: formData.get("name"),
type: formData.get("type"),
},
});
if (result) {
toast.success("Chatbot created!");
}
};
File Upload with FormData:
const { execute } = useHttp();
const handleFileUpload = async (file: File) => {
const formData = new FormData();
formData.append("file", file);
formData.append("user_id", userId);
// Content-Type header automatically removed for FormData
await execute("https://api.example.com/upload", {
method: "POST",
body: formData,
});
};
Used By¶
usePromptManager(9 API calls)- Various page components for one-off API requests
2. useSessionData Hook¶
File: src/hooks/useSessionData.ts (44 lines)
Purpose: Centralized session storage management for user and project IDs
Type Definitions¶
Hook Signature¶
State Variables (2)¶
- sessionData (
SessionData | null) - User and project IDs from sessionStorage - error (
string) - Error message if data is missing
Functionality¶
Data Retrieval:
const getUserData = (): SessionData | null => {
try {
const userData = {
user_id: sessionStorage.getItem("user_id") || "",
project_id: sessionStorage.getItem("selected_project_id") || "",
};
if (!userData.user_id || !userData.project_id) {
setError("Missing user_id or project_id in session storage");
return null;
}
return userData;
} catch (err) {
setError("Failed to read session data");
return null;
}
};
Session ID Initialization:
// Initialize session ID if not exists
let storedSessionId = sessionStorage.getItem("session_id");
if (!storedSessionId) {
storedSessionId = `session_${Math.random().toString(36).substr(2, 9)}`;
sessionStorage.setItem("session_id", storedSessionId);
}
Usage Patterns¶
Standard Usage:
const { sessionData, error } = useSessionData();
if (error) {
return <Error message="Please log in to continue" />;
}
if (!sessionData) {
return <Loader />;
}
// Use sessionData.user_id and sessionData.project_id
With API Calls:
const { sessionData } = useSessionData();
const { execute } = useHttp();
useEffect(() => {
if (sessionData) {
execute(
`/api/chatbots?user_id=${sessionData.user_id}&project_id=${sessionData.project_id}`
);
}
}, [sessionData]);
SessionStorage Keys¶
| Key | Value Type | Source | Description |
|---|---|---|---|
user_id |
string | Login response | Unique user identifier |
selected_project_id |
string | Project selection | Currently active chatbot project |
session_id |
string | Auto-generated | Anonymous session tracking |
Used By¶
usePromptManager(requires sessionData for all API calls)- Dashboard pages
- Chatbot management pages
3. usePromptManager Hook¶
File: src/hooks/usePromptManager.ts (331 lines)
Purpose: Complete LLM prompt and AI model management system
FULL DOCUMENTATION: See frontend-detailed.md section on usePromptManager
Summary:
- 12 state variables
- 9 API methods
- Manages system prompts, AI model selection, prompt purposes
- Handles state synchronization across multiple UI components
4. useUserFeatures Hook¶
File: src/hooks/useUserFeatures.ts (130 lines)
Purpose: Feature flag management and subscription-based limits
FULL DOCUMENTATION: See frontend-detailed.md section on useUserFeatures
Summary:
- Fetches user feature flags from backend
- Provides
isFeatureEnabled(featureName)helper - Handles 50MB vs 700MB upload limits
- Includes utility functions for file size formatting
5. useGlobalVideoManager Hook¶
File: src/hooks/useGlobalVideoManager.ts (177 lines)
Purpose: Global video playback coordination across components
Type Definitions¶
interface UseGlobalVideoManagerProps {
videoCount: number;
componentName: string; // For debugging
}
Hook Signature¶
const useGlobalVideoManager = ({ videoCount, componentName }: UseGlobalVideoManagerProps): {
videoRefs: MutableRefObject<HTMLVideoElement[]>;
playingVideos: Set<number>;
handlePlayPause: (index: number) => void;
isPlaying: (index: number) => boolean;
}
Global Event System¶
Purpose: Cross-component communication for video synchronization
const globalVideoEvents = {
listeners: new Map<string, Set<() => void>>(),
emit(event: string) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.forEach((listener) => listener());
}
},
on(event: string, callback: () => void) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
},
off(event: string, callback: () => void) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.delete(callback);
}
},
};
Event: videoStarted
- Emitted: When any video starts playing
- Effect: Pauses all other videos across all components
State Variables (2)¶
- videoRefs (
MutableRefObject<HTMLVideoElement[]>) - References to video DOM elements - playingVideos (
Set<number>) - Indices of currently playing videos
Core Functionality¶
Video Registration¶
With Mobile Video Manager:
useEffect(() => {
videoRefs.current.forEach((vid, index) => {
if (vid) {
mobileVideoManager.registerVideo(vid);
}
});
return () => {
videoRefs.current.forEach((vid) => {
if (vid) {
mobileVideoManager.unregisterVideo(vid);
}
});
};
}, [videoCount]);
Event Listeners¶
Play/Pause/Ended Events:
useEffect(() => {
videoRefs.current.forEach((vid, index) => {
if (!vid) return;
const handlePlay = () => {
// Emit global event to pause other components
globalVideoEvents.emit("videoStarted");
// Update local state
setPlayingVideos((prev) => new Set([index]));
};
const handlePause = () => {
setPlayingVideos((prev) => {
const newSet = new Set(prev);
newSet.delete(index);
return newSet;
});
};
const handleEnded = () => {
setPlayingVideos((prev) => {
const newSet = new Set(prev);
newSet.delete(index);
return newSet;
});
};
vid.addEventListener("play", handlePlay);
vid.addEventListener("pause", handlePause);
vid.addEventListener("ended", handleEnded);
return () => {
vid.removeEventListener("play", handlePlay);
vid.removeEventListener("pause", handlePause);
vid.removeEventListener("ended", handleEnded);
};
});
}, [videoCount, componentName]);
Global Event Handling¶
Pause on Other Component's Video Start:
useEffect(() => {
const handleOtherVideoStarted = () => {
// Pause all videos in this component when another component's video starts
videoRefs.current.forEach((vid, index) => {
if (vid && !vid.paused) {
vid.pause();
}
});
setPlayingVideos(new Set());
};
globalVideoEvents.on("videoStarted", handleOtherVideoStarted);
return () => {
globalVideoEvents.off("videoStarted", handleOtherVideoStarted);
};
}, [componentName]);
Periodic State Sync¶
Ensures UI state matches actual video state:
useEffect(() => {
const syncInterval = setInterval(() => {
const actuallyPlaying = new Set<number>();
videoRefs.current.forEach((vid, index) => {
if (vid && !vid.paused && !vid.ended) {
actuallyPlaying.add(index);
}
});
if (
actuallyPlaying.size !== playingVideos.size ||
!Array.from(actuallyPlaying).every((index) => playingVideos.has(index))
) {
setPlayingVideos(actuallyPlaying);
}
}, 500); // Check every 500ms
return () => clearInterval(syncInterval);
}, [playingVideos, componentName]);
Methods (2)¶
1. handlePlayPause(index: number)¶
Purpose: Toggles play/pause for a specific video
Features:
- Checks video existence
- Pauses if playing
- Plays if paused (unmutes, resets to start)
- Fallback to muted play if unmuted fails (browser autoplay policies)
Implementation:
const handlePlayPause = useCallback(
(index: number) => {
const videoElement = videoRefs.current[index];
if (!videoElement) {
console.warn(
`${componentName}: Video element at index ${index} not found`
);
return;
}
if (playingVideos.has(index)) {
// Pause the video
mobileVideoManager.allowUserPause(videoElement);
videoElement.pause();
} else {
// Play the video - this will trigger the global event
videoElement.muted = false;
videoElement.currentTime = 0;
videoElement.play().catch((error) => {
console.warn(`${componentName}: Video play failed:`, error);
// Try muted play if unmuted fails
videoElement.muted = true;
videoElement
.play()
.catch((err) =>
console.warn(`${componentName}: Muted play also failed:`, err)
);
});
}
},
[playingVideos, componentName]
);
2. isPlaying(index: number)¶
Purpose: Check if a specific video is playing
Returns: boolean
Implementation:
const isPlaying = useCallback(
(index: number) => {
return playingVideos.has(index);
},
[playingVideos]
);
Usage Example¶
import { useGlobalVideoManager } from "@/hooks/useGlobalVideoManager";
function VideoSection() {
const { videoRefs, playingVideos, handlePlayPause, isPlaying } =
useGlobalVideoManager({
videoCount: 3,
componentName: "HeroSection",
});
return (
<div>
{[0, 1, 2].map((index) => (
<div key={index}>
<video
ref={(el) => {
if (el) videoRefs.current[index] = el;
}}
src={`/videos/demo${index + 1}.mp4`}
/>
<button onClick={() => handlePlayPause(index)}>
{isPlaying(index) ? "⏸️ Pause" : "▶️ Play"}
</button>
</div>
))}
</div>
);
}
Mobile Video Manager Integration¶
External Dependency: @/utils/mobileVideoManager
Purpose: Handles mobile-specific video constraints (iOS restrictions, etc.)
Methods Used:
registerVideo(video: HTMLVideoElement)- Registers video for mobile optimizationunregisterVideo(video: HTMLVideoElement)- Cleanup on unmountallowUserPause(video: HTMLVideoElement)- Signals user-initiated pause (vs automatic)
Key Features¶
- Single Video Playback: Only one video plays at a time across entire site
- Cross-Component Coordination: Videos in different components communicate via global events
- Mobile Optimization: Integrates with mobile video manager for iOS compatibility
- Auto-Recovery: Fallback to muted play if browser blocks unmuted autoplay
- State Synchronization: Periodic checks ensure UI matches actual video state
- Debugging Support: Component name logging for troubleshooting
Used By¶
VideoDemoSection(homepage)AboutUsVideocomponent- Any component with multiple video elements
Hooks Dependency Graph¶
graph TB
A[useSessionData] --> B[usePromptManager]
C[useHttp] --> B
D[useUserFeatures] --> E[FileUploadComponent]
F[useGlobalVideoManager] --> G[VideoDemoSection]
F --> H[AboutUsVideo]
style A fill:#4CAF50
style B fill:#FF9800
style C fill:#2196F3
style D fill:#9C27B0
style F fill:#F44336
Hooks Summary Table¶
| Hook | Lines | State Vars | Methods | API Calls | Purpose |
|---|---|---|---|---|---|
useHttp |
75 | 3 | 2 | 0 | Generic HTTP client |
useSessionData |
44 | 2 | 0 | 0 | Session storage access |
usePromptManager |
331 | 12 | 9 | 6 endpoints | Prompt/LLM management |
useUserFeatures |
130 | 3 | 1 | 1 endpoint | Feature flags |
useGlobalVideoManager |
177 | 2 | 2 | 0 | Video coordination |
| TOTAL | 757 lines | 22 | 14 | 7 unique | 5 hooks |
Related Documentation¶
Last Updated: 2025-12-29
Coverage: 100% (5/5 hooks fully documented)
Owner: Frontend Lead
"Every hook, every line, every detail documented."