Advanced data structure management hooks providing optimized operations for Map, Set, dynamic lists, selections, and history management with built-in state synchronization.
Manages Map state with convenient methods for common map operations.
/**
* Manages Map state with convenient methods
* @param initialValue - Initial map entries as iterable
* @returns Array with Map instance and actions object
*/
function useMap<K, T>(initialValue?: Iterable<readonly [K, T]>): [Map<K, T>, MapActions<K, T>];
interface MapActions<K, T> {
/** Set a key-value pair */
set: (key: K, entry: T) => void;
/** Replace entire map with new entries */
setAll: (newMap: Iterable<readonly [K, T]>) => void;
/** Remove entry by key */
remove: (key: K) => void;
/** Clear all entries */
reset: () => void;
/** Get value by key (returns undefined if not found) */
get: (key: K) => T | undefined;
}Usage Example:
import { useMap } from 'ahooks';
import { useState } from 'react';
function UserManagement() {
// Initialize with some users
const [userMap, userActions] = useMap<number, { name: string; role: string }>([
[1, { name: 'Alice', role: 'admin' }],
[2, { name: 'Bob', role: 'user' }]
]);
const [newUserName, setNewUserName] = useState('');
const [newUserRole, setNewUserRole] = useState('user');
// Add new user
const addUser = () => {
if (newUserName) {
const newId = Math.max(0, ...Array.from(userMap.keys())) + 1;
userActions.set(newId, { name: newUserName, role: newUserRole });
setNewUserName('');
}
};
// Update user role
const updateUserRole = (id: number, role: string) => {
const user = userActions.get(id);
if (user) {
userActions.set(id, { ...user, role });
}
};
// Bulk operations
const loadSampleUsers = () => {
userActions.setAll([
[1, { name: 'Charlie', role: 'admin' }],
[2, { name: 'David', role: 'moderator' }],
[3, { name: 'Eve', role: 'user' }]
]);
};
return (
<div>
<h2>User Management with Map</h2>
<div>
<input
value={newUserName}
onChange={(e) => setNewUserName(e.target.value)}
placeholder="User name"
/>
<select value={newUserRole} onChange={(e) => setNewUserRole(e.target.value)}>
<option value="user">User</option>
<option value="moderator">Moderator</option>
<option value="admin">Admin</option>
</select>
<button onClick={addUser}>Add User</button>
</div>
<div>
<button onClick={loadSampleUsers}>Load Sample Users</button>
<button onClick={userActions.reset}>Clear All</button>
</div>
<div>
<h3>Users ({userMap.size})</h3>
{Array.from(userMap.entries()).map(([id, user]) => (
<div key={id} style={{ border: '1px solid #ddd', padding: '10px', margin: '5px' }}>
<strong>{user.name}</strong> (ID: {id})
<div>
Role:
<select
value={user.role}
onChange={(e) => updateUserRole(id, e.target.value)}
>
<option value="user">User</option>
<option value="moderator">Moderator</option>
<option value="admin">Admin</option>
</select>
</div>
<button onClick={() => userActions.remove(id)}>Remove</button>
</div>
))}
</div>
</div>
);
}Manages Set state with convenient methods for set operations.
/**
* Manages Set state with convenient methods
* @param initialValue - Initial set values as iterable
* @returns Array with Set instance and actions object
*/
function useSet<K>(initialValue?: Iterable<K>): [Set<K>, SetActions<K>];
interface SetActions<K> {
/** Add value to set */
add: (key: K) => void;
/** Remove value from set */
remove: (key: K) => void;
/** Clear all values */
reset: () => void;
/** Check if value exists in set */
has: (key: K) => boolean;
}Usage Example:
import { useSet } from 'ahooks';
import { useState } from 'react';
function TagManager() {
const [tags, tagActions] = useSet<string>(['react', 'typescript']);
const [availableTags] = useState(['react', 'typescript', 'javascript', 'nodejs', 'css', 'html']);
const [newTag, setNewTag] = useState('');
const addCustomTag = () => {
if (newTag && !tags.has(newTag)) {
tagActions.add(newTag);
setNewTag('');
}
};
const toggleTag = (tag: string) => {
if (tagActions.has(tag)) {
tagActions.remove(tag);
} else {
tagActions.add(tag);
}
};
return (
<div>
<h2>Tag Manager with Set</h2>
<div>
<h3>Available Tags</h3>
{availableTags.map(tag => (
<button
key={tag}
onClick={() => toggleTag(tag)}
style={{
margin: '2px',
backgroundColor: tagActions.has(tag) ? '#007bff' : '#f8f9fa',
color: tagActions.has(tag) ? 'white' : 'black',
border: '1px solid #dee2e6',
padding: '4px 8px',
borderRadius: '4px'
}}
>
{tag} {tagActions.has(tag) ? '✓' : '+'}
</button>
))}
</div>
<div>
<h3>Add Custom Tag</h3>
<input
value={newTag}
onChange={(e) => setNewTag(e.target.value)}
placeholder="Enter custom tag"
/>
<button onClick={addCustomTag}>Add Tag</button>
</div>
<div>
<h3>Selected Tags ({tags.size})</h3>
<div>
{Array.from(tags).map(tag => (
<span
key={tag}
style={{
display: 'inline-block',
margin: '2px',
padding: '4px 8px',
backgroundColor: '#28a745',
color: 'white',
borderRadius: '12px',
fontSize: '14px'
}}
>
{tag}
<button
onClick={() => tagActions.remove(tag)}
style={{
marginLeft: '4px',
background: 'none',
border: 'none',
color: 'white',
cursor: 'pointer'
}}
>
×
</button>
</span>
))}
</div>
<button onClick={tagActions.reset}>Clear All</button>
</div>
</div>
);
}Manages dynamic list with unique keys and comprehensive list operations.
/**
* Manages dynamic list with unique keys and list operations
* @param initialList - Initial list items
* @returns Object with list and action methods
*/
function useDynamicList<T>(initialList?: T[]): DynamicListActions<T>;
interface DynamicListActions<T> {
/** Current list items */
list: T[];
/** Insert item at specific index */
insert: (index: number, item: T) => void;
/** Merge multiple items at specific index */
merge: (index: number, items: T[]) => void;
/** Replace item at specific index */
replace: (index: number, item: T) => void;
/** Remove item at specific index */
remove: (index: number) => void;
/** Remove multiple items by indices */
batchRemove: (indexes: number[]) => void;
/** Get unique key for item at index */
getKey: (index: number) => number;
/** Get index by unique key */
getIndex: (key: number) => number;
/** Move item from one index to another */
move: (oldIndex: number, newIndex: number) => void;
/** Add item to end of list */
push: (item: T) => void;
/** Remove and return last item */
pop: () => void;
/** Add item to beginning of list */
unshift: (item: T) => void;
/** Remove and return first item */
shift: () => void;
/** Sort list with result array */
sortList: (result: T[]) => T[];
/** Replace entire list */
resetList: (newList: T[]) => void;
}Usage Example:
import { useDynamicList } from 'ahooks';
import { useState } from 'react';
interface TodoItem {
text: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
}
function TodoManager() {
const todoList = useDynamicList<TodoItem>([
{ text: 'Learn React', completed: false, priority: 'high' },
{ text: 'Build a project', completed: false, priority: 'medium' }
]);
const [newTodo, setNewTodo] = useState('');
const [newPriority, setNewPriority] = useState<'low' | 'medium' | 'high'>('medium');
const addTodo = () => {
if (newTodo.trim()) {
todoList.push({
text: newTodo.trim(),
completed: false,
priority: newPriority
});
setNewTodo('');
}
};
const toggleComplete = (index: number) => {
const item = todoList.list[index];
if (item) {
todoList.replace(index, { ...item, completed: !item.completed });
}
};
const updateTodo = (index: number, text: string) => {
const item = todoList.list[index];
if (item) {
todoList.replace(index, { ...item, text });
}
};
const sortByPriority = () => {
const priorityOrder = { high: 3, medium: 2, low: 1 };
const sorted = [...todoList.list].sort((a, b) =>
priorityOrder[b.priority] - priorityOrder[a.priority]
);
todoList.sortList(sorted);
};
const removeDoneTodos = () => {
const indicesToRemove = todoList.list
.map((item, index) => item.completed ? index : -1)
.filter(index => index !== -1)
.reverse(); // Remove from end to avoid index shifting
todoList.batchRemove(indicesToRemove);
};
return (
<div>
<h2>Dynamic Todo List</h2>
<div style={{ marginBottom: '20px' }}>
<input
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="Enter new todo"
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
/>
<select value={newPriority} onChange={(e) => setNewPriority(e.target.value as any)}>
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
<button onClick={addTodo}>Add Todo</button>
</div>
<div style={{ marginBottom: '20px' }}>
<button onClick={sortByPriority}>Sort by Priority</button>
<button onClick={removeDoneTodos}>Remove Completed</button>
<button onClick={() => todoList.resetList([])}>Clear All</button>
<span style={{ marginLeft: '10px' }}>
Total: {todoList.list.length} |
Completed: {todoList.list.filter(item => item.completed).length}
</span>
</div>
<div>
{todoList.list.map((item, index) => (
<div
key={todoList.getKey(index)}
style={{
display: 'flex',
alignItems: 'center',
padding: '10px',
margin: '5px 0',
border: '1px solid #ddd',
borderRadius: '4px',
backgroundColor: item.completed ? '#f0f8ff' : 'white'
}}
>
<input
type="checkbox"
checked={item.completed}
onChange={() => toggleComplete(index)}
/>
<input
value={item.text}
onChange={(e) => updateTodo(index, e.target.value)}
style={{
flex: 1,
margin: '0 10px',
textDecoration: item.completed ? 'line-through' : 'none'
}}
/>
<span
style={{
padding: '2px 6px',
borderRadius: '3px',
fontSize: '12px',
backgroundColor:
item.priority === 'high' ? '#ff6b6b' :
item.priority === 'medium' ? '#ffd93d' : '#6bcf7f',
color: item.priority === 'medium' ? 'black' : 'white'
}}
>
{item.priority}
</span>
<button
onClick={() => index > 0 && todoList.move(index, index - 1)}
disabled={index === 0}
>
↑
</button>
<button
onClick={() => index < todoList.list.length - 1 && todoList.move(index, index + 1)}
disabled={index === todoList.list.length - 1}
>
↓
</button>
<button onClick={() => todoList.remove(index)}>×</button>
</div>
))}
</div>
</div>
);
}Manages multiple item selection state with convenient selection methods.
/**
* Manages multiple item selection state
* @param items - Array of items that can be selected
* @param defaultSelected - Initially selected items
* @returns Object with selection state and action methods
*/
function useSelections<T>(items: T[], defaultSelected?: T[]): SelectionActions<T>;
interface SelectionActions<T> {
/** Currently selected items */
selected: T[];
/** Check if item is selected */
isSelected: (value: T) => boolean;
/** Select an item */
select: (value: T) => void;
/** Unselect an item */
unSelect: (value: T) => void;
/** Toggle item selection */
toggle: (value: T) => void;
/** Select all items */
selectAll: () => void;
/** Unselect all items */
unSelectAll: () => void;
/** True if no items selected */
noneSelected: boolean;
/** True if all items selected */
allSelected: boolean;
/** True if some but not all items selected */
partiallySelected: boolean;
}Usage Example:
import { useSelections } from 'ahooks';
import { useState } from 'react';
interface Product {
id: number;
name: string;
price: number;
category: string;
}
function ProductSelector() {
const products: Product[] = [
{ id: 1, name: 'Laptop', price: 999, category: 'Electronics' },
{ id: 2, name: 'Book', price: 29, category: 'Education' },
{ id: 3, name: 'Phone', price: 699, category: 'Electronics' },
{ id: 4, name: 'Desk', price: 199, category: 'Furniture' },
{ id: 5, name: 'Chair', price: 149, category: 'Furniture' }
];
const selection = useSelections(products, [products[0]]); // Pre-select first item
const [filter, setFilter] = useState('');
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(filter.toLowerCase()) ||
product.category.toLowerCase().includes(filter.toLowerCase())
);
const selectByCategory = (category: string) => {
products
.filter(product => product.category === category)
.forEach(product => selection.select(product));
};
const totalPrice = selection.selected.reduce((sum, product) => sum + product.price, 0);
const getSelectionState = () => {
if (selection.noneSelected) return 'none';
if (selection.allSelected) return 'all';
if (selection.partiallySelected) return 'partial';
return 'unknown';
};
return (
<div>
<h2>Product Selection Manager</h2>
<div style={{ marginBottom: '20px' }}>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter products..."
style={{ marginRight: '10px' }}
/>
<label>
<input
type="checkbox"
checked={selection.allSelected}
ref={input => {
if (input) input.indeterminate = selection.partiallySelected;
}}
onChange={() => {
if (selection.allSelected || selection.partiallySelected) {
selection.unSelectAll();
} else {
selection.selectAll();
}
}}
/>
Select All ({selection.selected.length}/{products.length})
</label>
</div>
<div style={{ marginBottom: '20px' }}>
<strong>Quick Select:</strong>
<button onClick={() => selectByCategory('Electronics')}>
Electronics
</button>
<button onClick={() => selectByCategory('Furniture')}>
Furniture
</button>
<button onClick={() => selectByCategory('Education')}>
Education
</button>
<button onClick={selection.unSelectAll}>Clear All</button>
</div>
<div style={{ marginBottom: '20px' }}>
<strong>Selection Status:</strong> {getSelectionState()} |
<strong> Total Price:</strong> ${totalPrice}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '10px' }}>
{filteredProducts.map(product => (
<div
key={product.id}
onClick={() => selection.toggle(product)}
style={{
border: `2px solid ${selection.isSelected(product) ? '#007bff' : '#ddd'}`,
padding: '15px',
borderRadius: '8px',
cursor: 'pointer',
backgroundColor: selection.isSelected(product) ? '#e7f3ff' : 'white'
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h4>{product.name}</h4>
<input
type="checkbox"
checked={selection.isSelected(product)}
onChange={() => {}} // Handled by div onClick
/>
</div>
<p>Category: {product.category}</p>
<p><strong>${product.price}</strong></p>
</div>
))}
</div>
{selection.selected.length > 0 && (
<div style={{ marginTop: '20px', padding: '15px', backgroundColor: '#f8f9fa', borderRadius: '8px' }}>
<h3>Selected Products:</h3>
<ul>
{selection.selected.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
<button
onClick={() => selection.unSelect(product)}
style={{ marginLeft: '10px' }}
>
Remove
</button>
</li>
))}
</ul>
</div>
)}
</div>
);
}Manages state history with undo/redo functionality for complex state management.
/**
* Manages state history with undo/redo functionality
* @param initialValue - Initial state value
* @param maxLength - Maximum history length (default: 10)
* @returns Object with current value and history navigation methods
*/
function useHistoryTravel<T>(initialValue?: T, maxLength?: number): HistoryTravelActions<T>;
interface HistoryTravelActions<T> {
/** Current value */
value: T | undefined;
/** Number of steps backward possible */
backLength: number;
/** Number of steps forward possible */
forwardLength: number;
/** Set new value (creates history entry) */
setValue: (val: T) => void;
/** Go forward or backward by number of steps */
go: (step: number) => void;
/** Go back one step */
back: () => void;
/** Go forward one step */
forward: () => void;
/** Reset history with optional new value */
reset: (...params: any[]) => void;
}Usage Example:
import { useHistoryTravel } from 'ahooks';
import { useState } from 'react';
interface DrawingState {
paths: Array<{ x: number; y: number; color: string }>;
currentColor: string;
brushSize: number;
}
function DrawingApp() {
const initialState: DrawingState = {
paths: [],
currentColor: '#000000',
brushSize: 5
};
const history = useHistoryTravel<DrawingState>(initialState, 50); // Keep 50 history steps
const [isDrawing, setIsDrawing] = useState(false);
const addPoint = (x: number, y: number) => {
if (!history.value) return;
const newState: DrawingState = {
...history.value,
paths: [
...history.value.paths,
{ x, y, color: history.value.currentColor }
]
};
history.setValue(newState);
};
const changeColor = (color: string) => {
if (!history.value) return;
history.setValue({
...history.value,
currentColor: color
});
};
const changeBrushSize = (size: number) => {
if (!history.value) return;
history.setValue({
...history.value,
brushSize: size
});
};
const clearCanvas = () => {
history.setValue({
...initialState,
currentColor: history.value?.currentColor || '#000000',
brushSize: history.value?.brushSize || 5
});
};
return (
<div>
<h2>Drawing App with History</h2>
<div style={{ marginBottom: '10px' }}>
<button
onClick={history.back}
disabled={history.backLength === 0}
>
↶ Undo ({history.backLength})
</button>
<button
onClick={history.forward}
disabled={history.forwardLength === 0}
>
↷ Redo ({history.forwardLength})
</button>
<button onClick={clearCanvas}>Clear</button>
<button onClick={() => history.reset()}>Reset All</button>
</div>
<div style={{ marginBottom: '10px' }}>
<label>Color: </label>
<input
type="color"
value={history.value?.currentColor || '#000000'}
onChange={(e) => changeColor(e.target.value)}
/>
<label style={{ marginLeft: '20px' }}>Brush Size: </label>
<input
type="range"
min="1"
max="20"
value={history.value?.brushSize || 5}
onChange={(e) => changeBrushSize(parseInt(e.target.value))}
/>
<span>{history.value?.brushSize}</span>
</div>
<svg
width="600"
height="400"
style={{ border: '1px solid #ccc', cursor: 'crosshair' }}
onMouseDown={(e) => {
setIsDrawing(true);
const rect = e.currentTarget.getBoundingClientRect();
addPoint(e.clientX - rect.left, e.clientY - rect.top);
}}
onMouseMove={(e) => {
if (!isDrawing) return;
const rect = e.currentTarget.getBoundingClientRect();
addPoint(e.clientX - rect.left, e.clientY - rect.top);
}}
onMouseUp={() => setIsDrawing(false)}
onMouseLeave={() => setIsDrawing(false)}
>
{history.value?.paths.map((point, index) => (
<circle
key={index}
cx={point.x}
cy={point.y}
r={history.value?.brushSize || 5}
fill={point.color}
/>
))}
</svg>
<div style={{ marginTop: '10px' }}>
<p>Total points: {history.value?.paths.length || 0}</p>
<p>History position: {history.backLength} / {history.backLength + history.forwardLength + 1}</p>
</div>
</div>
);
}// Map actions interface
interface MapActions<K, T> {
set: (key: K, entry: T) => void;
setAll: (newMap: Iterable<readonly [K, T]>) => void;
remove: (key: K) => void;
reset: () => void;
get: (key: K) => T | undefined;
}
// Set actions interface
interface SetActions<K> {
add: (key: K) => void;
remove: (key: K) => void;
reset: () => void;
has: (key: K) => boolean;
}
// Selection actions interface
interface SelectionActions<T> {
selected: T[];
isSelected: (value: T) => boolean;
select: (value: T) => void;
unSelect: (value: T) => void;
toggle: (value: T) => void;
selectAll: () => void;
unSelectAll: () => void;
noneSelected: boolean;
allSelected: boolean;
partiallySelected: boolean;
}