Advanced effect hooks for lifecycle management, async operations, performance optimization, and enhanced useEffect patterns.
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>;
}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>;
}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;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>
);
}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 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>
);
}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;
}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;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>
);
}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>;
}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>;
}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>;
}// React effect types
type EffectCallback = () => void | (() => void);
type DependencyList = ReadonlyArray<any>;