Skip to content

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

const useHttp = <T = any>(): UseHttpReturn<T>

State Variables (3)

  1. data (T | null) - Response data
  2. loading (boolean) - Request in progress
  3. 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 request
  • options (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:

const reset = useCallback(() => {
  setData(null);
  setError(null);
  setLoading(false);
}, []);

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

interface SessionData {
  user_id: string;
  project_id: string;
}

Hook Signature

const useSessionData = (): {
  sessionData: SessionData | null;
  error: string
}

State Variables (2)

  1. sessionData (SessionData | null) - User and project IDs from sessionStorage
  2. 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)

  1. videoRefs (MutableRefObject<HTMLVideoElement[]>) - References to video DOM elements
  2. 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 optimization
  • unregisterVideo(video: HTMLVideoElement) - Cleanup on unmount
  • allowUserPause(video: HTMLVideoElement) - Signals user-initiated pause (vs automatic)

Key Features

  1. Single Video Playback: Only one video plays at a time across entire site
  2. Cross-Component Coordination: Videos in different components communicate via global events
  3. Mobile Optimization: Integrates with mobile video manager for iOS compatibility
  4. Auto-Recovery: Fallback to muted play if browser blocks unmuted autoplay
  5. State Synchronization: Periodic checks ensure UI matches actual video state
  6. Debugging Support: Component name logging for troubleshooting

Used By

  • VideoDemoSection (homepage)
  • AboutUsVideo component
  • 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


Last Updated: 2025-12-29
Coverage: 100% (5/5 hooks fully documented)
Owner: Frontend Lead


"Every hook, every line, every detail documented."