or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async-operations.mddata-structures.mddom-interactions.mdeffects.mdindex.mdperformance.mdspecialized-hooks.mdstate-management.mdstorage.mdtimers.md
tile.json

data-structures.mddocs/

Data Structures

Advanced data structure management hooks providing optimized operations for Map, Set, dynamic lists, selections, and history management with built-in state synchronization.

Capabilities

Map Management

useMap

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>
  );
}

Set Management

useSet

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>
  );
}

Dynamic Lists

useDynamicList

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>
  );
}

Selection Management

useSelections

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>
  );
}

History Management

useHistoryTravel

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>
  );
}

Common Types

// 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;
}