Advanced and specialized hooks for complex use cases including table management, virtual lists, WebSocket connections, browser APIs, drag & drop, and developer tools.
Comprehensive table management for Ant Design with forms, pagination, and search functionality.
/**
* Manages Ant Design Table with forms and pagination
* @param service - Async function that returns table data
* @param options - Configuration options for table behavior
* @returns Object with table props, search controls, and request state
*/
function useAntdTable<TData extends Data, TParams extends Params>(
service: Service<TData, TParams>,
options?: AntdTableOptions<TData, TParams>
): AntdTableResult<TData, TParams>;
// Data structure for paginated results
interface Data {
list: any[];
total: number;
}
type Params = [any, any]; // [pagination, formData]
// Service function type
type Service<TData extends Data, TParams extends Params> = (...args: TParams) => Promise<TData>;
// Result interface
interface AntdTableResult<TData extends Data, TParams extends Params> {
// Table props for Ant Design Table component
tableProps: {
dataSource: TData['list'];
loading: boolean;
onChange: (pagination: any, filters?: any, sorter?: any) => void;
pagination: any;
};
// Search functionality
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
// Request controls (inherited from useRequest)
loading: boolean;
data?: TData;
error?: Error;
params?: TParams;
run: (...params: TParams) => void;
runAsync: (...params: TParams) => Promise<TData>;
refresh: () => void;
refreshAsync: () => Promise<TData>;
cancel: () => void;
mutate: (data?: TData) => void;
}
interface AntdTableOptions<TData extends Data, TParams extends Params> {
// Pagination
defaultPageSize?: number;
// Form integration
form?: any; // Ant Design Form instance
defaultType?: 'simple' | 'advance';
// Search behavior
manual?: boolean;
refreshDeps?: any[];
// Data processing
formatResult?: (data: any) => TData;
// Callbacks
onSuccess?: (data: TData, params: TParams) => void;
onError?: (error: Error, params: TParams) => void;
}Usage Example:
import { useAntdTable } from 'ahooks';
import { Table, Form, Input, Button, Card } from 'antd';
import { useState } from 'react';
interface User {
id: number;
name: string;
email: string;
role: string;
createdAt: string;
}
interface UserSearchParams {
name?: string;
email?: string;
role?: string;
}
function UserTable() {
const [form] = Form.useForm();
// Service function that returns paginated data
const getUserList = async (
{ current, pageSize }: { current: number; pageSize: number },
formData: UserSearchParams
) => {
const params = new URLSearchParams({
page: current.toString(),
size: pageSize.toString(),
...formData
});
const response = await fetch(`/api/users?${params}`);
const data = await response.json();
return {
list: data.users,
total: data.total
};
};
const { tableProps, search } = useAntdTable(getUserList, {
defaultPageSize: 10,
form,
formatResult: (data) => ({
list: data.users || [],
total: data.total || 0
}),
onSuccess: (data, params) => {
console.log('Table data loaded:', data.list.length, 'items');
}
});
const columns = [
{
title: 'Name',
dataIndex: 'name',
sorter: true
},
{
title: 'Email',
dataIndex: 'email'
},
{
title: 'Role',
dataIndex: 'role',
filters: [
{ text: 'Admin', value: 'admin' },
{ text: 'User', value: 'user' },
{ text: 'Moderator', value: 'moderator' }
]
},
{
title: 'Created',
dataIndex: 'createdAt',
sorter: true,
render: (date: string) => new Date(date).toLocaleDateString()
},
{
title: 'Actions',
render: (_, user: User) => (
<div>
<Button size="small" onClick={() => editUser(user.id)}>Edit</Button>
<Button size="small" danger onClick={() => deleteUser(user.id)}>Delete</Button>
</div>
)
}
];
const editUser = (id: number) => {
console.log('Edit user:', id);
};
const deleteUser = (id: number) => {
console.log('Delete user:', id);
// Refresh table after deletion
search.submit();
};
return (
<div>
<Card>
<Form form={form} onFinish={search.submit}>
<div style={{ display: 'flex', gap: '16px', marginBottom: '16px' }}>
<Form.Item name="name" style={{ margin: 0 }}>
<Input placeholder="Search by name" />
</Form.Item>
<Form.Item name="email" style={{ margin: 0 }}>
<Input placeholder="Search by email" />
</Form.Item>
{search.type === 'advance' && (
<Form.Item name="role" style={{ margin: 0 }}>
<Input placeholder="Search by role" />
</Form.Item>
)}
<Button type="primary" htmlType="submit">
Search
</Button>
<Button onClick={search.reset}>Reset</Button>
<Button type="link" onClick={search.changeType}>
{search.type === 'simple' ? 'Advanced' : 'Simple'} Search
</Button>
</div>
</Form>
</Card>
<Table
{...tableProps}
columns={columns}
rowKey="id"
style={{ marginTop: '16px' }}
/>
</div>
);
}Fusion Design table management hook built on top of useAntdTable, providing integration with Fusion Design Form and Table components.
/**
* Manages Fusion Design Table with forms and pagination
* @param service - Async function that returns table data
* @param options - Configuration options including field instance
* @returns Object with table props, pagination props, and search controls
*/
function useFusionTable<TData extends Data, TParams extends Params>(
service: Service<TData, TParams>,
options?: FusionTableOptions<TData, TParams>
): FusionTableResult<TData, TParams>;
// Fusion-specific field interface
interface Field {
getFieldInstance?: (name: string) => Record<string, any>;
setValues: (value: Record<string, any>) => void;
getValues: (...args: any) => Record<string, any>;
reset: (...args: any) => void;
validate: (fields: any, callback: (errors, values) => void) => void;
[key: string]: any;
}
// Result interface for Fusion Table
interface FusionTableResult<TData extends Data, TParams extends Params> {
// Pagination props for Fusion Pagination component
paginationProps: {
onChange: (current: number) => void;
onPageSizeChange: (size: number) => void;
current: number;
pageSize: number;
total: number;
};
// Table props for Fusion Table component
tableProps: {
dataSource: TData['list'];
loading: boolean;
onSort: (dataIndex: string, order: string) => void;
onFilter: (filterParams: any) => void;
};
// Search functionality
search: {
type: 'simple' | 'advance';
changeType: () => void;
submit: () => void;
reset: () => void;
};
// Request controls (inherited from useRequest)
loading: boolean;
data?: TData;
error?: Error;
params?: TParams;
run: (...params: TParams) => void;
runAsync: (...params: TParams) => Promise<TData>;
refresh: () => void;
refreshAsync: () => Promise<TData>;
cancel: () => void;
mutate: (data?: TData) => void;
}
interface FusionTableOptions<TData extends Data, TParams extends Params> {
// Fusion Form field instance
field?: Field;
// Default form type
defaultType?: 'simple' | 'advance';
// Pagination
defaultPageSize?: number;
// Default parameters [pagination, formData]
defaultParams?: TParams;
// Search behavior
manual?: boolean;
refreshDeps?: any[];
// Request options (inherited from useRequest)
onSuccess?: (data: TData, params: TParams) => void;
onError?: (error: Error, params: TParams) => void;
}Usage Example:
import { useFusionTable } from 'ahooks';
import { Table, Form, Field, Input, Button, Pagination } from '@alifd/next';
interface User {
id: number;
name: string;
email: string;
role: string;
}
interface UserTableData {
list: User[];
total: number;
}
function FusionUserTable() {
const field = Field.useField();
// Service function that returns paginated data
const getUserList = async ([pagination, formData]: [any, any]): Promise<UserTableData> => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
current: pagination.current,
pageSize: pagination.pageSize,
...formData
})
});
return response.json();
};
const { tableProps, paginationProps, search, loading } = useFusionTable(getUserList, {
field,
defaultPageSize: 10,
defaultType: 'simple'
});
const columns = [
{
title: 'Name',
dataIndex: 'name',
sortable: true
},
{
title: 'Email',
dataIndex: 'email'
},
{
title: 'Role',
dataIndex: 'role',
filters: [
{ text: 'Admin', value: 'admin' },
{ text: 'User', value: 'user' }
]
}
];
return (
<div>
<Form field={field} onSubmit={search.submit}>
<Form.Item>
<Input name="name" placeholder="Search by name" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">Search</Button>
<Button onClick={search.reset}>Reset</Button>
<Button onClick={search.changeType}>
Switch to {search.type === 'simple' ? 'Advanced' : 'Simple'}
</Button>
</Form.Item>
</Form>
<Table
{...tableProps}
columns={columns}
primaryKey="id"
/>
<Pagination {...paginationProps} />
</div>
);
}Generic pagination management for any data source.
/**
* Generic pagination management
* @param service - Async function that returns paginated data
* @param options - Configuration options
* @returns Object with pagination state and controls
*/
function usePagination<TData extends Data, TParams extends Params>(
service: Service<TData, TParams>,
options?: PaginationOptions<TData, TParams>
): PaginationResult<TData, TParams>;
interface PaginationResult<TData extends Data, TParams extends Params> {
data: TData | undefined;
loading: boolean;
pagination: {
current: number;
pageSize: number;
total: number;
totalPage: number;
onChange: (current: number, pageSize: number) => void;
changeCurrent: (current: number) => void;
changePageSize: (pageSize: number) => void;
};
// Standard request controls
error?: Error;
params?: TParams;
run: (...params: TParams) => void;
runAsync: (...params: TParams) => Promise<TData>;
refresh: () => void;
refreshAsync: () => Promise<TData>;
cancel: () => void;
mutate: (data?: TData) => void;
}Manages infinite scrolling for loading more data as user scrolls.
/**
* Manages infinite scrolling for loading more data
* @param service - Async function that returns data for next page
* @param options - Configuration options
* @returns Object with data, loading states, and load more controls
*/
function useInfiniteScroll<TData extends Data>(
service: Service<TData>,
options?: InfiniteScrollOptions<TData>
): InfiniteScrollResult<TData>;
interface InfiniteScrollResult<TData extends Data> {
/** Accumulated data from all loaded pages */
data: TData;
/** Loading state for initial load */
loading: boolean;
/** Loading state for loading more data */
loadingMore: boolean;
/** Error object if any request failed */
error?: Error;
/** Whether there's no more data to load */
noMore: boolean;
/** Load next page of data */
loadMore: () => void;
/** Load next page and return promise */
loadMoreAsync: () => Promise<TData>;
/** Reload from first page */
reload: () => void;
/** Reload from first page and return promise */
reloadAsync: () => Promise<TData>;
/** Cancel ongoing request */
cancel: () => void;
/** Update data without request */
mutate: (data?: TData) => void;
}Renders large lists efficiently by only rendering visible items.
/**
* Renders large lists efficiently using virtualization
* @param list - Array of data items to virtualize
* @param options - Configuration options for virtualization
* @returns Object with visible items and scroll control
*/
function useVirtualList<T = any>(list: T[], options: Options<T>): VirtualListReturn<T>;
interface Options<T> {
/** Container element reference */
containerTarget: BasicTarget;
/** Wrapper element reference (scrollable container) */
wrapperTarget: BasicTarget;
/** Height of each item (number or function) */
itemHeight: number | ((index: number, data: T) => number);
/** Number of items to render outside visible area (default: 5) */
overscan?: number;
}
interface VirtualListReturn<T> {
/** Array of visible items with their data and original index */
list: Array<{ data: T; index: number }>;
/** Function to scroll to specific item index */
scrollTo: (index: number) => void;
}
type BasicTarget = Element | (() => Element | null) | React.MutableRefObject<Element | null>;Usage Example:
import { useVirtualList } from 'ahooks';
import { useRef, useMemo } from 'react';
function LargeList() {
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null);
// Generate large dataset
const largeData = useMemo(() =>
Array.from({ length: 100000 }, (_, index) => ({
id: index,
name: `Item ${index}`,
description: `Description for item ${index}`,
value: Math.floor(Math.random() * 1000)
}))
, []);
const { list, scrollTo } = useVirtualList(largeData, {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 60, // Fixed height for each item
overscan: 10 // Render 10 extra items outside visible area
});
const jumpToItem = (index: number) => {
scrollTo(index);
};
return (
<div>
<h2>Virtual List (100,000 items)</h2>
<div style={{ marginBottom: '10px' }}>
<button onClick={() => jumpToItem(0)}>Jump to Start</button>
<button onClick={() => jumpToItem(50000)}>Jump to Middle</button>
<button onClick={() => jumpToItem(99999)}>Jump to End</button>
<button onClick={() => jumpToItem(Math.floor(Math.random() * largeData.length))}>
Jump to Random
</button>
</div>
<div
ref={wrapperRef}
style={{
height: '400px',
overflow: 'auto',
border: '1px solid #ccc'
}}
>
<div ref={containerRef}>
{list.map(({ data, index }) => (
<div
key={index}
style={{
height: '60px',
padding: '10px',
borderBottom: '1px solid #eee',
display: 'flex',
alignItems: 'center',
backgroundColor: index % 2 === 0 ? '#f9f9f9' : 'white'
}}
>
<div style={{ flex: 1 }}>
<div style={{ fontWeight: 'bold' }}>{data.name}</div>
<div style={{ fontSize: '12px', color: '#666' }}>
{data.description}
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div>Index: {index}</div>
<div>Value: {data.value}</div>
</div>
</div>
))}
</div>
</div>
<p>Only rendering visible items. Scroll to see more!</p>
</div>
);
}Manages WebSocket connections with automatic reconnection and comprehensive event handling.
/**
* Manages WebSocket connections with reconnection
* @param socketUrl - WebSocket URL to connect to
* @param options - Configuration options
* @returns Object with connection state and control methods
*/
function useWebSocket(socketUrl: string, options?: Options): Result;
interface Options {
/** Maximum reconnection attempts (default: 3) */
reconnectLimit?: number;
/** Interval between reconnection attempts in ms (default: 3000) */
reconnectInterval?: number;
/** Don't connect automatically on mount */
manual?: boolean;
/** WebSocket protocols */
protocols?: string | string[];
// Event callbacks
onOpen?: (event: WebSocketEventMap['open'], instance: WebSocket) => void;
onClose?: (event: WebSocketEventMap['close'], instance: WebSocket) => void;
onMessage?: (message: WebSocketEventMap['message'], instance: WebSocket) => void;
onError?: (event: WebSocketEventMap['error'], instance: WebSocket) => void;
}
interface Result {
/** Latest received message */
latestMessage?: WebSocketEventMap['message'];
/** Send message function */
sendMessage: WebSocket['send'];
/** Disconnect WebSocket */
disconnect: () => void;
/** Connect WebSocket */
connect: () => void;
/** Current connection state */
readyState: ReadyState;
/** WebSocket instance */
webSocketIns?: WebSocket;
}
enum ReadyState {
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3,
}Usage Example:
import { useWebSocket, ReadyState } from 'ahooks';
import { useState, useCallback } from 'react';
interface ChatMessage {
id: string;
user: string;
message: string;
timestamp: number;
}
function ChatApp() {
const [messageHistory, setMessageHistory] = useState<ChatMessage[]>([]);
const [currentMessage, setCurrentMessage] = useState('');
const [username, setUsername] = useState('User' + Math.floor(Math.random() * 1000));
const {
sendMessage,
latestMessage,
readyState,
connect,
disconnect
} = useWebSocket('ws://localhost:8080/chat', {
onOpen: () => {
console.log('Connected to chat server');
setMessageHistory(prev => [...prev, {
id: Date.now().toString(),
user: 'System',
message: 'Connected to chat',
timestamp: Date.now()
}]);
},
onClose: () => {
console.log('Disconnected from chat server');
},
onError: (event) => {
console.error('WebSocket error:', event);
},
reconnectLimit: 5,
reconnectInterval: 3000
});
// Handle incoming messages
const handleMessage = useCallback(() => {
if (latestMessage?.data) {
try {
const message: ChatMessage = JSON.parse(latestMessage.data);
setMessageHistory(prev => [...prev, message]);
} catch (error) {
console.error('Failed to parse message:', error);
}
}
}, [latestMessage]);
// Effect to handle new messages
useEffect(() => {
handleMessage();
}, [handleMessage]);
const sendChatMessage = () => {
if (currentMessage.trim() && readyState === ReadyState.Open) {
const message: ChatMessage = {
id: Date.now().toString(),
user: username,
message: currentMessage.trim(),
timestamp: Date.now()
};
sendMessage(JSON.stringify(message));
setCurrentMessage('');
}
};
const connectionStatus = {
[ReadyState.Connecting]: 'Connecting',
[ReadyState.Open]: 'Connected',
[ReadyState.Closing]: 'Closing',
[ReadyState.Closed]: 'Disconnected',
}[readyState];
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<h2>WebSocket Chat</h2>
<div style={{ marginBottom: '20px' }}>
<div>Status: <strong>{connectionStatus}</strong></div>
<div>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="Username"
style={{ marginRight: '10px' }}
/>
<button onClick={connect} disabled={readyState === ReadyState.Open}>
Connect
</button>
<button onClick={disconnect} disabled={readyState !== ReadyState.Open}>
Disconnect
</button>
</div>
</div>
<div
style={{
height: '300px',
border: '1px solid #ccc',
padding: '10px',
overflowY: 'auto',
marginBottom: '10px',
backgroundColor: '#f9f9f9'
}}
>
{messageHistory.map((msg) => (
<div key={msg.id} style={{ marginBottom: '8px' }}>
<strong>{msg.user}:</strong> {msg.message}
<span style={{ fontSize: '12px', color: '#666', marginLeft: '10px' }}>
{new Date(msg.timestamp).toLocaleTimeString()}
</span>
</div>
))}
</div>
<div style={{ display: 'flex' }}>
<input
value={currentMessage}
onChange={(e) => setCurrentMessage(e.target.value)}
placeholder="Type a message..."
style={{ flex: 1, marginRight: '10px' }}
onKeyPress={(e) => e.key === 'Enter' && sendChatMessage()}
disabled={readyState !== ReadyState.Open}
/>
<button
onClick={sendChatMessage}
disabled={readyState !== ReadyState.Open || !currentMessage.trim()}
>
Send
</button>
</div>
</div>
);
}Tracks network connection status and connection quality information.
/**
* Tracks network connection status and quality
* @returns Object with network state information
*/
function useNetwork(): NetworkState;
interface NetworkState {
/** Date when connection status last changed */
since?: Date;
/** Whether browser is online */
online?: boolean;
/** Round-trip time estimate in ms */
rtt?: number;
/** Connection type (4g, 3g, etc.) */
type?: string;
/** Downlink speed estimate in Mbps */
downlink?: number;
/** Whether user has data saver enabled */
saveData?: boolean;
/** Maximum downlink speed in Mbps */
downlinkMax?: number;
/** Effective connection type (slow-2g, 2g, 3g, 4g) */
effectiveType?: string;
}Manages responsive breakpoints and screen size detection.
/**
* Manages responsive breakpoints
* @returns Object with boolean flags for each breakpoint
*/
function useResponsive(): ResponsiveInfo;
type ResponsiveInfo = Record<string, boolean>;
/**
* Configures custom breakpoints for useResponsive
* @param config - Object with breakpoint names and pixel values
*/
function configResponsive(config: ResponsiveConfig): void;
type ResponsiveConfig = Record<string, number>;Tracks document visibility state (visible, hidden, prerender).
/**
* Tracks document visibility state
* @returns Current visibility state
*/
function useDocumentVisibility(): VisibilityState;
type VisibilityState = 'hidden' | 'visible' | 'prerender' | undefined;Manages document title with optional restoration on unmount.
/**
* Manages document title
* @param title - New document title
* @param restoreOnUnmount - Whether to restore original title on unmount
*/
function useTitle(title: string, restoreOnUnmount?: boolean): void;Manages document favicon.
/**
* Manages document favicon
* @param href - URL or data URI for favicon
*/
function useFavicon(href: string): void;Handles drag operations with customizable drag data and visual feedback.
/**
* Handles drag operations
* @param data - Data to transfer during drag
* @param target - Element to make draggable
* @param options - Drag configuration options
*/
function useDrag<T>(data: T, target: BasicTarget, options?: Options): void;
interface Options {
onDragStart?: (event: React.DragEvent) => void;
onDragEnd?: (event: React.DragEvent) => void;
dragImage?: {
image: string | Element;
offsetX?: number;
offsetY?: number;
};
}Handles drop operations with support for files, text, and custom data.
/**
* Handles drop operations
* @param target - Element to make a drop target
* @param options - Drop configuration options
*/
function useDrop(target: BasicTarget, options?: Options): void;
interface Options {
onFiles?: (files: File[], event?: React.DragEvent) => void;
onUri?: (url: string, event?: React.DragEvent) => void;
onDom?: (content: any, event?: React.DragEvent) => void;
onText?: (text: string, event?: React.ClipboardEvent) => void;
onDragEnter?: (event?: React.DragEvent) => void;
onDragOver?: (event?: React.DragEvent) => void;
onDragLeave?: (event?: React.DragEvent) => void;
onDrop?: (event?: React.DragEvent) => void;
onPaste?: (event?: React.ClipboardEvent) => void;
}Tracks prop changes that cause component re-renders for debugging performance issues.
/**
* Tracks prop changes that cause re-renders
* @param name - Component name for logging
* @param props - Object with props to track
*/
function useWhyDidYouUpdate(name: string, props: Record<string, any>): void;Forces component re-render, useful for debugging or manual updates.
/**
* Forces component re-render
* @returns Function to trigger re-render
*/
function useUpdate(): () => void;Returns ref indicating whether component has been unmounted.
/**
* Returns ref indicating if component is unmounted
* @returns Ref object with current boolean indicating unmount status
*/
function useUnmountedRef(): MutableRefObject<boolean>;Loads external JavaScript and CSS resources dynamically.
/**
* Loads external JavaScript and CSS resources
* @param path - URL of external resource
* @param options - Loading options
* @returns Loading status
*/
function useExternal(path?: string, options?: Options): Status;
type Status = 'unset' | 'loading' | 'ready' | 'error';
type Options = JsOptions | CssOptions | DefaultOptions;
interface JsOptions {
type: 'js';
js?: Partial<HTMLScriptElement>;
keepWhenUnused?: boolean;
}
interface CssOptions {
type: 'css';
css?: Partial<HTMLStyleElement>;
keepWhenUnused?: boolean;
}Manages form input state with convenient change handler.
/**
* Manages form input state
* @param options - Configuration options
* @returns Array with current value and actions
*/
function useEventTarget<T, U = T>(options?: Options<T, U>): [T | undefined, EventTargetActions];
interface Options<T, U> {
initialValue?: T;
transformer?: (value: U) => T;
}
interface EventTargetActions {
onChange: (e: { target: { value: any } }) => void;
reset: () => void;
}Manages application theme state.
/**
* Manages theme state
* @returns Array with current theme and setter function
*/
function useTheme(): [string | undefined, (theme: string) => void];Creates event emitter for component-to-component communication.
/**
* Creates event emitter for component communication
* @returns EventEmitter instance
*/
function useEventEmitter<T = void>(): EventEmitter<T>;
class EventEmitter<T> {
/** Emit event with data */
emit: (val: T) => void;
/** Subscribe to events */
useSubscription: (callback: (val: T) => void) => void;
}// Basic target element type
type BasicTarget<T = Element> = (() => T | null) | T | null | React.MutableRefObject<T>;
// React ref type
type MutableRefObject<T> = React.MutableRefObject<T>;
// Data structure for paginated results
interface Data {
list: any[];
total: number;
}
// Network connection state
interface NetworkState {
since?: Date;
online?: boolean;
rtt?: number;
type?: string;
downlink?: number;
saveData?: boolean;
downlinkMax?: number;
effectiveType?: string;
}
// WebSocket ready states
enum ReadyState {
Connecting = 0,
Open = 1,
Closing = 2,
Closed = 3,
}