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

effects.mddocs/

Effects

Advanced effect hooks for lifecycle management, async operations, performance optimization, and enhanced useEffect patterns.

Capabilities

Lifecycle Effects

useMount

Executes a function only when component mounts, with support for both synchronous and asynchronous operations.

/**
 * Executes function only on component mount
 * @param fn - Function to execute on mount, can return cleanup function or Promise
 */
function useMount(fn: MountCallback): void;

type MountCallback = EffectCallback | (() => Promise<void | (() => void)>);
type EffectCallback = () => void | (() => void);

Usage Example:

import { useMount } from 'ahooks';

function DataLoader() {
  const [data, setData] = useState(null);
  
  // Synchronous mount effect
  useMount(() => {
    console.log('Component mounted');
    
    // Return cleanup function
    return () => {
      console.log('Cleanup on unmount');
    };
  });
  
  // Asynchronous mount effect
  useMount(async () => {
    const response = await fetch('/api/data');
    const result = await response.json();
    setData(result);
    
    // Can return cleanup function from async
    return () => {
      // Cleanup logic
    };
  });
  
  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}

useUnmount

Executes a function only when component unmounts for cleanup operations.

/**
 * Executes function only on component unmount
 * @param fn - Cleanup function to execute on unmount
 */
function useUnmount(fn: () => void): void;

Usage Example:

import { useUnmount } from 'ahooks';

function WebSocketComponent() {
  const [socket, setSocket] = useState<WebSocket | null>(null);
  
  useEffect(() => {
    const ws = new WebSocket('ws://localhost:8080');
    setSocket(ws);
  }, []);
  
  useUnmount(() => {
    // Cleanup WebSocket connection
    if (socket) {
      socket.close();
    }
    console.log('Component unmounted, WebSocket cleaned up');
  });
  
  return <div>WebSocket Status: {socket?.readyState}</div>;
}

Update Effects

useUpdateEffect

useEffect that skips the first render, only running on subsequent updates.

/**
 * useEffect that skips the first render and only runs on updates
 * @param effect - Effect function to run on updates
 * @param deps - Dependency array (optional)
 */
function useUpdateEffect(effect: EffectCallback, deps?: DependencyList): void;

useUpdateLayoutEffect

useLayoutEffect that skips the first render, only running on subsequent updates.

/**
 * useLayoutEffect that skips the first render and only runs on updates
 * @param effect - Effect function to run on updates
 * @param deps - Dependency array (optional)
 */
function useUpdateLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;

Usage Example:

import { useUpdateEffect } from 'ahooks';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  // This won't run on first render, only when query changes
  useUpdateEffect(() => {
    if (query) {
      fetch(`/api/search?q=${query}`)
        .then(res => res.json())
        .then(setResults);
    } else {
      setResults([]);
    }
  }, [query]);
  
  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <ul>
        {results.map(item => <li key={item.id}>{item.title}</li>)}
      </ul>
    </div>
  );
}

Async Effects

useAsyncEffect

Handles async effects and async generators with proper cleanup and cancellation.

/**
 * Handles async effects and async generators
 * @param effect - Async function or async generator function
 * @param deps - Dependency array (optional)
 */
function useAsyncEffect(
  effect: () => AsyncGenerator<void, void, void> | Promise<void>,
  deps?: DependencyList
): void;

Usage Example:

import { useAsyncEffect } from 'ahooks';

function AsyncDataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  
  // Promise-based async effect
  useAsyncEffect(async () => {
    setLoading(true);
    try {
      const response = await fetch('/api/data');
      const result = await response.json();
      setData(result);
    } catch (error) {
      console.error('Failed to fetch data:', error);
    } finally {
      setLoading(false);
    }
  }, []);
  
  // Async generator for complex async workflows
  useAsyncEffect(async function* () {
    yield; // Wait for component to be ready
    
    setLoading(true);
    const step1 = await fetch('/api/step1');
    const step1Data = await step1.json();
    
    yield; // Yield control, can be cancelled here
    
    const step2 = await fetch(`/api/step2/${step1Data.id}`);
    const finalData = await step2.json();
    
    setData(finalData);
    setLoading(false);
  }, []);
  
  return <div>{loading ? 'Loading...' : JSON.stringify(data)}</div>;
}

Debounced Effects

useDebounceEffect

Debounced version of useEffect that delays execution until dependencies stop changing.

/**
 * Debounced version of useEffect
 * @param effect - Effect function to run after debounce delay
 * @param deps - Dependency array (optional)
 * @param options - Debounce configuration options
 */
function useDebounceEffect(
  effect: EffectCallback,
  deps?: DependencyList,
  options?: DebounceOptions
): void;

interface DebounceOptions {
  /** Delay in milliseconds (default: 1000) */
  wait?: number;
  /** Execute on leading edge (default: false) */
  leading?: boolean;
  /** Execute on trailing edge (default: true) */
  trailing?: boolean;
  /** Maximum delay before execution (default: undefined) */
  maxWait?: number;
}

Usage Example:

import { useDebounceEffect } from 'ahooks';

function SearchWithDebounce() {
  const [searchTerm, setSearchTerm] = useState('');
  const [results, setResults] = useState([]);
  
  // Debounce search API calls - only search 500ms after user stops typing
  useDebounceEffect(() => {
    if (searchTerm) {
      fetch(`/api/search?q=${searchTerm}`)
        .then(res => res.json())
        .then(setResults);
    } else {
      setResults([]);
    }
  }, [searchTerm], {
    wait: 500,
    leading: false,
    trailing: true
  });
  
  return (
    <div>
      <input 
        placeholder="Search..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <ul>
        {results.map(item => <li key={item.id}>{item.title}</li>)}
      </ul>
    </div>
  );
}

useThrottleEffect

Throttled version of useEffect that limits execution frequency.

/**
 * Throttled version of useEffect that limits execution frequency
 * @param effect - Effect function to run with throttling
 * @param deps - Dependency array (optional)
 * @param options - Throttle configuration options
 */
function useThrottleEffect(
  effect: EffectCallback,
  deps?: DependencyList,
  options?: ThrottleOptions
): void;

interface ThrottleOptions {
  /** Throttle interval in milliseconds (default: 1000) */
  wait?: number;
  /** Execute on leading edge (default: true) */
  leading?: boolean;
  /** Execute on trailing edge (default: false) */
  trailing?: boolean;
}

Deep Comparison Effects

useDeepCompareEffect

useEffect with deep comparison of dependencies instead of reference equality.

/**
 * useEffect with deep comparison of dependencies
 * @param effect - Effect function to run when dependencies deeply change
 * @param deps - Dependency array for deep comparison
 */
function useDeepCompareEffect(effect: EffectCallback, deps?: DependencyList): void;

useDeepCompareLayoutEffect

useLayoutEffect with deep comparison of dependencies instead of reference equality.

/**
 * useLayoutEffect with deep comparison of dependencies
 * @param effect - Effect function to run when dependencies deeply change
 * @param deps - Dependency array for deep comparison
 */
function useDeepCompareLayoutEffect(effect: EffectCallback, deps?: DependencyList): void;

Usage Example:

import { useDeepCompareEffect } from 'ahooks';

function UserList() {
  const [users, setUsers] = useState([]);
  const [filters, setFilters] = useState({ status: 'active', role: 'user' });
  
  // This will only run when filters object actually changes in content,
  // not just reference
  useDeepCompareEffect(() => {
    fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(filters)
    })
    .then(res => res.json())
    .then(setUsers);
  }, [filters]); // Deep comparison prevents unnecessary API calls
  
  const updateFilters = (newFilters) => {
    // Even if this creates a new object with same values,
    // the effect won't run unnecessarily
    setFilters({ ...filters, ...newFilters });
  };
  
  return (
    <div>
      <button onClick={() => updateFilters({ status: 'active' })}>
        Show Active
      </button>
      <ul>
        {users.map(user => <li key={user.id}>{user.name}</li>)}
      </ul>
    </div>
  );
}

Debugging Effects

useTrackedEffect

Tracks which dependency caused an effect to run, useful for debugging complex effects.

/**
 * Tracks which dependency caused effect to run for debugging
 * @param effect - Effect function that receives change information
 * @param deps - Dependency array to track
 */
function useTrackedEffect(
  effect: (changes?: number[], previousDeps?: DependencyList, currentDeps?: DependencyList) => void,
  deps?: DependencyList
): void;

Usage Example:

import { useTrackedEffect } from 'ahooks';

function DebugComponent({ userId, filters, sortOrder }) {
  useTrackedEffect((changes, prevDeps, currentDeps) => {
    console.log('Effect triggered by dependencies at indices:', changes);
    console.log('Previous dependencies:', prevDeps);
    console.log('Current dependencies:', currentDeps);
    
    // Your effect logic here
    fetchUserData(userId, filters, sortOrder);
  }, [userId, filters, sortOrder]);
  
  return <div>Check console for dependency change tracking</div>;
}

SSR Compatible Effects

useIsomorphicLayoutEffect

useLayoutEffect for SSR environments, falls back to useEffect on the server.

/**
 * useLayoutEffect that falls back to useEffect in SSR environments
 * Ensures compatibility between client and server rendering
 */
const useIsomorphicLayoutEffect: typeof useLayoutEffect | typeof useEffect;

Usage Example:

import { useIsomorphicLayoutEffect } from 'ahooks';

function ResponsiveComponent() {
  const [windowSize, setWindowSize] = useState({ width: 0, height: 0 });
  
  useIsomorphicLayoutEffect(() => {
    function updateSize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }
    
    updateSize();
    window.addEventListener('resize', updateSize);
    
    return () => window.removeEventListener('resize', updateSize);
  }, []);
  
  return <div>Window size: {windowSize.width} x {windowSize.height}</div>;
}

Utility Functions

createUpdateEffect

Factory function for creating custom update-only effect hooks.

/**
 * Factory for creating update-only effect hooks
 * @param hook - Effect hook type (useEffect or useLayoutEffect)
 * @returns Update-only version of the provided hook
 */
function createUpdateEffect(hook: EffectHookType): EffectHookType;

type EffectHookType = typeof useEffect | typeof useLayoutEffect;

Usage Example:

import { createUpdateEffect } from 'ahooks';
import { useLayoutEffect } from 'react';

// Create custom update-only layout effect
const useUpdateLayoutEffect = createUpdateEffect(useLayoutEffect);

function CustomComponent() {
  const [count, setCount] = useState(0);
  
  // This will only run on updates, not on mount
  useUpdateLayoutEffect(() => {
    // Layout effect logic that skips first render
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}

Common Types

// React effect types
type EffectCallback = () => void | (() => void);
type DependencyList = ReadonlyArray<any>;