Files
openhuman/docs/src/08-hooks-utils.md
T
Steven EnamakelandGitHub 58969667d9 fix: add ESLint and Prettier configuration (#15)
* ran prettier

* Refactor ESLint configuration to use ES module syntax and enhance TypeScript support

- Converted CommonJS `require` statements to ES module `import` syntax for better compatibility with modern JavaScript.
- Added new paths to ignore in ESLint configuration to exclude additional directories.
- Updated TypeScript file patterns to be more specific, improving linting accuracy.
- Adjusted React hooks rules to allow certain patterns, enhancing flexibility in component design.

These changes improve the maintainability and clarity of the ESLint configuration, aligning it with current best practices.

* Refactor ESLint configuration to use ES module syntax and enhance TypeScript support

- Converted CommonJS `require` statements to ES module `import` syntax for better compatibility with modern JavaScript.
- Added new paths to ignore in ESLint configuration to exclude additional directories.
- Updated TypeScript file patterns to be more specific, improving linting accuracy.
- Introduced new React hooks rules and adjusted existing rules for better adherence to best practices.
- Made minor adjustments to import statements across various files for consistency and clarity.

These changes improve the overall linting setup and ensure better code quality across the project.

* Refactor import statements across multiple files for consistency

- Updated import statements to use TypeScript's `type` syntax for type imports, enhancing clarity and consistency across the codebase.
- Consolidated imports from the same module into single statements, improving readability and maintainability.

These changes streamline the code structure and align with best practices for TypeScript imports.

* Refactor import statements in memory manager for improved clarity

- Updated import statements to consolidate type imports and enhance readability.
- Removed redundant imports, streamlining the code structure in the memory manager file.

These changes align with best practices for TypeScript imports and improve maintainability.

* Add Husky for pre-commit and pre-push hooks

- Introduced Husky to manage Git hooks, enhancing the development workflow.
- Added pre-commit and pre-push scripts to enforce code formatting and linting checks before commits and pushes.
- Updated package.json to include Husky as a dependency and added a prepare script for setup.

These changes improve code quality and ensure adherence to formatting and linting standards during the development process.

* Refactor import statements for improved clarity and consistency

- Updated import statements across multiple files to consolidate type imports and enhance readability.
- Adjusted the order of imports for better organization and alignment with best practices in TypeScript.

These changes streamline the code structure and improve maintainability throughout the project.

* ran formatter

* Refactor import statements and improve code formatting across multiple files

- Consolidated and reordered import statements for better clarity and consistency in `SkillsGrid.tsx`, `SkillProvider.tsx`, and `index.ts`.
- Enhanced readability by adjusting formatting and removing redundant lines.
- These changes align with best practices for TypeScript imports and improve overall maintainability of the codebase.

* Refactor and optimize code in multiple components

- Removed redundant properties from the `STATUS_DISPLAY` object in `SkillsGrid.tsx` to streamline status handling.
- Consolidated import statements in `SettingsModal.tsx` for improved organization.
- Simplified state management and error handling in `BillingPanel.tsx`, enhancing performance and readability.
- Added `REHYDRATE` import to `index.ts` for better state persistence management.

These changes improve code clarity, maintainability, and align with best practices in TypeScript development.

* Consolidate import statements in SettingsModal.tsx for improved organization

* Add Prettier and ESLint checks to typecheck workflow

- Integrated a Prettier formatting check to ensure code style consistency.
- Added an ESLint step to enforce code quality and catch potential issues.
- These enhancements improve the development workflow by automating formatting and linting checks during the typecheck process.

* Add activeSkillDescription state to ConnectionsPanel and ConnectStep

- Introduced activeSkillDescription state in both ConnectionsPanel and ConnectStep components to store and manage skill descriptions.
- Updated the SkillSetupModal to accept skillDescription as a prop, enhancing the modal's functionality and data handling.

These changes improve the user experience by providing more detailed information about skills during the connection setup process.

* Enhance pre-push hook to include TypeScript compile check

- Added a TypeScript compile check to the pre-push script, ensuring that code compiles successfully before pushing.
- Updated error handling to include compile errors alongside formatting and linting issues, providing clearer feedback to developers.

These changes improve the reliability of the codebase by preventing non-compiling code from being pushed.

* Update GitHub workflows for pull request handling and publishing logic

- Modified the package-and-publish workflow to support pull request events, ensuring proper handling of branches.
- Adjusted the SHOULD_PUBLISH environment variable to differentiate between pull requests and main branch events.
- Updated the pr-protection workflow to focus solely on the main branch, removing references to the master branch.

These changes enhance the CI/CD process by refining branch handling and improving clarity in workflow conditions.
2026-02-02 06:24:50 +05:30

9.9 KiB

Hooks & Utilities

Custom React hooks and utility functions.

Custom Hooks

useSocket (hooks/useSocket.ts)

Access Socket.io functionality from any component.

interface UseSocketReturn {
  socket: Socket | null;
  isConnected: boolean;
  emit: (event: string, data: unknown) => void;
  on: (event: string, handler: Function) => void;
  off: (event: string, handler: Function) => void;
  once: (event: string, handler: Function) => void;
}

function useSocket(): UseSocketReturn;

Usage:

import { useSocket } from "../hooks/useSocket";

function ChatInput() {
  const { emit, isConnected } = useSocket();

  const sendMessage = (text: string) => {
    if (isConnected) {
      emit("chat:message", { text });
    }
  };

  return (
    <input
      disabled={!isConnected}
      onKeyDown={(e) => e.key === "Enter" && sendMessage(e.target.value)}
    />
  );
}

With event listeners:

function Notifications() {
  const { on, off } = useSocket();
  const [notifications, setNotifications] = useState([]);

  useEffect(() => {
    const handler = (notification) => {
      setNotifications((prev) => [...prev, notification]);
    };

    on("notification", handler);
    return () => off("notification", handler);
  }, [on, off]);

  return <NotificationList items={notifications} />;
}

useUser (hooks/useUser.ts)

Access user profile data and loading state.

interface UseUserReturn {
  user: User | null;
  loading: boolean;
  error: string | null;
  refetch: () => Promise<void>;
}

function useUser(): UseUserReturn;

Usage:

import { useUser } from "../hooks/useUser";

function ProfileHeader() {
  const { user, loading, error, refetch } = useUser();

  if (loading) return <Skeleton />;
  if (error) return <Error message={error} onRetry={refetch} />;
  if (!user) return null;

  return (
    <div className="profile">
      <Avatar src={user.avatar} />
      <span>
        {user.firstName} {user.lastName}
      </span>
    </div>
  );
}

Settings Modal Hooks

useSettingsNavigation (components/settings/hooks/useSettingsNavigation.ts)

URL-based navigation for settings modal.

interface UseSettingsNavigationReturn {
  currentRoute: string; // Current settings path
  navigateTo: (panel: string) => void; // Navigate to panel
  navigateBack: () => void; // Go back one level
  closeModal: () => void; // Close settings entirely
}

function useSettingsNavigation(): UseSettingsNavigationReturn;

Usage:

import { useSettingsNavigation } from "./hooks/useSettingsNavigation";

function SettingsMenu() {
  const { navigateTo, closeModal } = useSettingsNavigation();

  return (
    <nav>
      <button onClick={() => navigateTo("connections")}>Connections</button>
      <button onClick={() => navigateTo("privacy")}>Privacy</button>
      <button onClick={closeModal}>Close</button>
    </nav>
  );
}

useSettingsAnimation (components/settings/hooks/useSettingsAnimation.ts)

Animation state management for settings modal.

interface UseSettingsAnimationReturn {
  isEntering: boolean; // Modal is animating in
  isExiting: boolean; // Modal is animating out
  animationClass: string; // CSS class for current state
}

function useSettingsAnimation(): UseSettingsAnimationReturn;

Usage:

import { useSettingsAnimation } from "./hooks/useSettingsAnimation";

function SettingsModal() {
  const { animationClass, isExiting } = useSettingsAnimation();

  return <div className={`modal ${animationClass}`}>{/* Content */}</div>;
}

Utilities

Configuration (utils/config.ts)

Environment variable access with defaults.

// Backend URL
export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.example.com';

// Telegram configuration
export const TELEGRAM_API_ID = import.meta.env.VITE_TELEGRAM_API_ID;
export const TELEGRAM_API_HASH = import.meta.env.VITE_TELEGRAM_API_HASH;
export const TELEGRAM_BOT_USERNAME = import.meta.env.VITE_TELEGRAM_BOT_USERNAME;
export const TELEGRAM_BOT_ID = import.meta.env.VITE_TELEGRAM_BOT_ID;

// Debug mode
export const DEBUG = import.meta.env.VITE_DEBUG === 'true';

Usage:

import { BACKEND_URL, DEBUG } from '../utils/config';

const response = await fetch(`${BACKEND_URL}/api/users`);

if (DEBUG) {
  console.log('Response:', response);
}

Build deep link URLs for authentication handoff.

// Build auth deep link
function buildAuthDeepLink(token: string): string;

// Parse deep link URL
function parseDeepLink(url: string): { path: string; params: URLSearchParams };

Usage:

import { buildAuthDeepLink } from '../utils/deeplink';

// Build URL for browser redirect
const deepLink = buildAuthDeepLink(loginToken);
// → "alphahuman://auth?token=abc123"

// In web frontend after auth:
window.location.href = deepLink;

Handle incoming deep links in desktop app.

// Setup listener for deep link events
async function setupDesktopDeepLinkListener(): Promise<void>;

Called in main.tsx:

// Lazy import to ensure Tauri IPC is ready
import('./utils/desktopDeepLinkListener').then(m => {
  m.setupDesktopDeepLinkListener().catch(console.error);
});

What it does:

  1. Listens for onOpenUrl events from Tauri deep-link plugin
  2. Parses alphahuman://auth?token=... URLs
  3. Calls Rust exchange_token command (bypasses CORS)
  4. Stores session in Redux
  5. Navigates to /onboarding or /home

Loop prevention:

// Set flag before navigation to prevent reprocessing
localStorage.setItem('deepLinkHandled', 'true');
window.location.replace('/');

// On next load, clear flag
if (localStorage.getItem('deepLinkHandled') === 'true') {
  localStorage.removeItem('deepLinkHandled');
  return; // Don't process again
}

URL Opener (utils/openUrl.ts)

Cross-platform URL opening.

// Open URL in system browser
async function openUrl(url: string): Promise<void>;

Usage:

import { openUrl } from '../utils/openUrl';

// Opens in system browser (not in-app WebView)
await openUrl('https://telegram.org/auth');

Implementation:

export async function openUrl(url: string): Promise<void> {
  try {
    // Try Tauri opener plugin first
    const { open } = await import('@tauri-apps/plugin-opener');
    await open(url);
  } catch {
    // Fallback to browser API
    window.open(url, '_blank');
  }
}

Polyfills (polyfills.ts)

Node.js polyfills for browser environment.

The telegram npm package requires Node.js APIs. These are polyfilled:

// polyfills.ts
import { Buffer } from 'buffer';
import process from 'process';
import util from 'util';

window.Buffer = Buffer;
window.process = process;
window.util = util;

Imported at app entry:

// main.tsx
import './polyfills';

// ... rest of app

Vite configuration:

// vite.config.ts
export default defineConfig({
  resolve: { alias: { buffer: 'buffer', process: 'process/browser', util: 'util' } },
  define: { 'process.env': {}, global: 'globalThis' },
});

Types

API Types (types/api.ts)

// API response wrapper
interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: string;
}

// API error
interface ApiError {
  code: string;
  message: string;
  details?: unknown;
}

// User interface
interface User {
  id: string;
  firstName: string;
  lastName?: string;
  username?: string;
  email?: string;
  avatar?: string;
  telegramId?: string;
  subscription?: SubscriptionInfo;
  usage?: UsageInfo;
  createdAt: string;
  updatedAt: string;
}

Onboarding Types (types/onboarding.ts)

// Onboarding step definition
interface OnboardingStep {
  id: string;
  title: string;
  component: React.ComponentType<StepProps>;
}

// Step component props
interface StepProps {
  onNext: () => void;
  onBack: () => void;
}

// Connection option
interface ConnectionOption {
  id: string;
  label: string;
  icon: React.ComponentType;
  description: string;
  comingSoon?: boolean;
}

Static Data

Countries (data/countries.ts)

Country list for phone number input.

interface Country {
  code: string; // "US"
  name: string; // "United States"
  dialCode: string; // "+1"
  flag: string; // "🇺🇸"
}

export const countries: Country[];

Usage:

import { countries } from "../data/countries";

function PhoneInput() {
  const [country, setCountry] = useState(countries[0]);

  return (
    <div>
      <select
        value={country.code}
        onChange={(e) =>
          setCountry(countries.find((c) => c.code === e.target.value))
        }
      >
        {countries.map((c) => (
          <option key={c.code} value={c.code}>
            {c.flag} {c.name} ({c.dialCode})
          </option>
        ))}
      </select>
      <input placeholder="Phone number" />
    </div>
  );
}

Best Practices

Hook Dependencies

Always include dependencies in useEffect:

// Good
useEffect(() => {
  on('event', handler);
  return () => off('event', handler);
}, [on, off, handler]);

// Bad - missing dependencies
useEffect(() => {
  on('event', handler);
  return () => off('event', handler);
}, []);

Cleanup Functions

Always clean up subscriptions:

useEffect(() => {
  const subscription = subscribe();
  return () => subscription.unsubscribe();
}, []);

Error Boundaries

Wrap utility calls in try-catch:

try {
  await openUrl(url);
} catch (error) {
  console.error('Failed to open URL:', error);
  // Fallback behavior
}

Type Safety

Use TypeScript generics for API calls:

const user = await apiClient.get<User>('/users/me');
// user is typed as User

Previous: Providers | Back to Index