Custom Hooks Reference
Complete reference for all custom React hooks in the Constellation project.
Hook Organization
All custom hooks are located in src/hooks/ with the naming convention use-*.tsx or use-*.ts.
Authentication Hooks
useIsAdmin
Check if current user is an admin.
import { useIsAdmin } from '@/src/hooks/use-is-admin';
export function AdminOnlyComponent() {
const isAdmin = useIsAdmin();
if (!isAdmin) return null;
return <div>Admin content</div>;
}
Returns: boolean
Usage: Protect admin-only features and routes
useRequireAdmin
Redirect to login if not admin.
import { useRequireAdmin } from '@/src/hooks/use-require-admin';
export function AdminPanel() {
useRequireAdmin(); // Redirects if not admin
return <div>Admin panel</div>;
}
Returns: void
Side Effect: Redirects user if not authorized
UI/UX Hooks
useMobile
Detect if viewport is mobile size.
import { useMobile } from '@/src/hooks/use-mobile';
export function ResponsiveComponent() {
const isMobile = useMobile();
if (isMobile) {
return <MobileLayout />;
}
return <DesktopLayout />;
}
Returns: boolean
Breakpoint: < 768px (Tailwind md breakpoint)
useIsTouchDevice
Detect if device supports touch.
import { useIsTouchDevice } from '@/src/hooks/use-is-touch-device';
export function InteractiveElement() {
const isTouchDevice = useIsTouchDevice();
return (
<button className={isTouchDevice ? 'large' : 'normal'}>
{isTouchDevice ? 'Tap' : 'Click'} me
</button>
);
}
Returns: boolean
Use Case: Adjust UI for touch interfaces
useMounted
Check if component is mounted (SSR safe).
import { useMounted } from '@/src/hooks/use-mounted';
export function ClientOnlyContent() {
const mounted = useMounted();
if (!mounted) return null; // Don't render on server
return <div>Client-only content</div>;
}
Returns: boolean
Use Case: Prevent hydration mismatch
Utility Hooks
useDebounce
Debounce a value.
import { useDebounce } from '@/src/hooks/use-debounce';
export function SearchUsers({ onSearch }: Props) {
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 500); // 500ms delay
useEffect(() => {
onSearch(debouncedSearch);
}, [debouncedSearch]);
return (
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search..."
/>
);
}
Parameters:
- value - Value to debounce
- delay - Debounce delay in ms (default: 500)
Returns: T - Debounced value
Use Case: Search inputs, API calls on input
useCopyToClipboard
Copy text to clipboard.
import { useCopyToClipboard } from '@/src/hooks/use-copy-to-clipboard';
export function ShareLink({ url }: Props) {
const { copy, copied } = useCopyToClipboard();
return (
<button onClick={() => copy(url)}>
{copied ? 'Copied!' : 'Copy Link'}
</button>
);
}
Methods:
- copy(text: string) - Copy text to clipboard
State:
- copied: boolean - Whether copy was successful
Use Case: Copy links, share buttons, code snippets
Workspace Hooks
useWorkspace
Get current workspace data.
import { useWorkspace } from '@/src/hooks/use-workspace';
export function WorkspaceEditor() {
const { workspace, loading, error } = useWorkspace();
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<div>
<h1>{workspace.name}</h1>
<Editor content={workspace.content} />
</div>
);
}
Returns:
{
workspace: Workspace | null;
loading: boolean;
error: Error | null;
}
Use Case: Fetch and display workspace data
Chat Hooks
useCustomChat
Manage chat state and messages.
import { useCustomChat } from '@/src/hooks/use-custom-chat';
export function ChatComponent() {
const { messages, sendMessage, loading } = useCustomChat();
const handleSend = async (text: string) => {
await sendMessage(text);
};
return (
<div>
{messages.map((msg) => (
<div key={msg.id}>{msg.content}</div>
))}
<ChatInput onSend={handleSend} />
</div>
);
}
Returns:
{
messages: ChatMessage[];
sendMessage: (text: string) => Promise<void>;
loading: boolean;
error: Error | null;
}
Use Case: Chatbot integration, real-time messaging
Collaboration Hooks
useYjs
Access Yjs collaborative data structures.
import { useYjs } from '@/src/hooks/use-yjs';
export function CollaborativeEditor() {
const { yText, yMap, connected } = useYjs('document');
if (!connected) return <div>Connecting...</div>;
return (
<Editor
text={yText.toString()}
onChange={(newText) => {
yText.delete(0, yText.length);
yText.insert(0, newText);
}}
/>
);
}
Returns:
{
yText: Y.Text; // Collaborative text
yMap: Y.Map; // Collaborative map
connected: boolean;
awareness: Awareness; // User awareness
}
Use Case: Real-time collaborative editing
Admin Hooks
useAdminPing
Check admin status with periodic refresh.
import { useAdminPing } from '@/src/hooks/use-admin-ping';
export function AdminStatus() {
const { isAdmin, refreshing } = useAdminPing();
return (
<div>
Admin: {isAdmin ? 'Yes' : 'No'}
{refreshing && ' (checking...)'}
</div>
);
}
Returns:
{
isAdmin: boolean;
refreshing: boolean;
refresh: () => Promise<void>;
}
Use Case: Periodic admin status checks
Language Hooks
useLanguageChange
Handle language switching.
import { useLanguageChange } from '@/src/hooks/use-language-change';
export function LanguageSwitcher() {
const { currentLanguage, setLanguage } = useLanguageChange();
return (
<select value={currentLanguage} onChange={(e) => setLanguage(e.target.value)}>
<option value="en">English</option>
<option value="fr">FranΓ§ais</option>
</select>
);
}
Returns:
{
currentLanguage: string;
setLanguage: (lang: string) => void;
}
Use Case: Language selection UI
Node Lookup Hook
useNodeByLabel
Find workspace nodes by label.
import { useNodeByLabel } from '@/src/hooks/use-node-by-label';
export function NodeSearch({ label }: Props) {
const { node, loading, error } = useNodeByLabel(label);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <NodeDetails node={node} />;
}
Parameters:
- label: string - Node label to search for
Returns:
{
node: Node | null;
loading: boolean;
error: Error | null;
}
Use Case: Graph node lookup and navigation
Creating Custom Hooks
Hook Template
// src/hooks/use-my-hook.ts
import { useEffect, useState, useCallback } from 'react';
interface UseMyHookReturn {
data: string | null;
loading: boolean;
error: Error | null;
refetch: () => Promise<void>;
}
/**
* Custom hook description
* @param param - Parameter description
* @returns Hook state and methods
* @example
* const { data, loading } = useMyHook('value');
*/
export function useMyHook(param: string): UseMyHookReturn {
const [data, setData] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const result = await someAsyncOperation(param);
setData(result);
} catch (err) {
setError(err instanceof Error ? err : new Error('Unknown error'));
} finally {
setLoading(false);
}
}, [param]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
Hook Test Template
// src/hooks/use-my-hook.test.ts
import { describe, it, expect } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { useMyHook } from './use-my-hook';
describe('useMyHook', () => {
it('should initialize with loading state', () => {
const { result } = renderHook(() => useMyHook('test'));
expect(result.current.loading).toBe(true);
});
it('should load data successfully', async () => {
const { result } = renderHook(() => useMyHook('test'));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.data).toBe('expected data');
});
it('should handle errors', async () => {
const { result } = renderHook(() => useMyHook('invalid'));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.error).toBeDefined();
});
});
Hook Best Practices
β Do's
- β Return state, setters, and methods in an object
- β Handle loading and error states
- β
Use meaningful names starting with
use - β Document with JSDoc
- β Write tests for hooks
- β Clean up side effects with return function
- β
Memoize callbacks with
useCallback - β
Use
useMemofor expensive computations
β Don'ts
- β Don't call hooks conditionally
- β Don't use hooks outside React components
- β Don't forget dependencies in
useEffect - β Don't make hooks too complex
- β Don't forget loading/error states
- β Don't forget cleanup functions
- β Don't use unnecessary state
- β Don't forget to export hooks
Hook Usage Index
| Hook | Purpose | Import |
|---|---|---|
useIsAdmin |
Check admin status | @/src/hooks/use-is-admin |
useRequireAdmin |
Require admin or redirect | @/src/hooks/use-require-admin |
useMobile |
Detect mobile viewport | @/src/hooks/use-mobile |
useIsTouchDevice |
Detect touch support | @/src/hooks/use-is-touch-device |
useMounted |
Check if mounted (SSR) | @/src/hooks/use-mounted |
useDebounce |
Debounce value | @/src/hooks/use-debounce |
useCopyToClipboard |
Copy to clipboard | @/src/hooks/use-copy-to-clipboard |
useWorkspace |
Get workspace data | @/src/hooks/use-workspace |
useCustomChat |
Chat state management | @/src/hooks/use-custom-chat |
useYjs |
Collaborative editing | @/src/hooks/use-yjs |
useAdminPing |
Admin status check | @/src/hooks/use-admin-ping |
useLanguageChange |
Language switching | @/src/hooks/use-language-change |
useNodeByLabel |
Graph node lookup | @/src/hooks/use-node-by-label |
Related Documentation
- ποΈ ARCHITECTURE.md - Hook architecture
- π COMPONENTS.md - Component usage
- π§ͺ TESTING.md - Hook testing
Last Updated: January 2026