# Role
You are a React Hooks Specialist who designs custom hooks with proper dependency management, cleanup, TypeScript types, and performance optimization.
# Task
Create a custom React hook that encapsulates specific logic with proper TypeScript types, dependency management, and cleanup handling.
# Instructions
**Hook Requirements:**
**Hook Purpose:**
- Hook name: [HOOK_NAME]
- What it does: [DESCRIPTION]
- Use case: [WHEN_TO_USE]
- Return value: [WHAT_IT_RETURNS]
**Functionality:**
```typescript
[DESCRIBE_HOOK_BEHAVIOR]
Examples:
- Fetch data from API
- Manage form state
- Handle WebSocket connection
- Debounce input
- Track element visibility
- Manage local storage
```
**Dependencies:**
- External dependencies: [LIBRARIES_OR_NONE]
- React version: [16.8+_17_18]
- TypeScript: [YES_NO]
**Performance Considerations:**
- Call frequency: [ONCE_OCCASIONAL_FREQUENT]
- Expensive operations: [YES_NO_DETAILS]
- Cleanup needed: [YES_NO_WHAT]
**Existing Code:**
```typescript
[PASTE_RELATED_CODE]
Include:
- Type definitions
- API interfaces
- Related hooks
```
Based on this information:
1. **Complete Hook Implementation:**
````typescript
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
/**
* [HOOK_DESCRIPTION]
*
* @param [PARAM_NAME] - [PARAM_DESCRIPTION]
* @returns [RETURN_DESCRIPTION]
*
* @example
* ```tsx
* const { data, loading, error } = use[HookName](params);
* ```
*/
export function use[HOOK_NAME]<T>(
[PARAMETERS]: [TYPES]
): [RETURN_TYPE] {
// State
const [state, setState] = useState<StateType>(initialState);
// Refs for stable references
const mountedRef = useRef(true);
const callbackRef = useRef(callback);
// Update callback ref when it changes
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
// Memoized values
const memoizedValue = useMemo(() => {
// Expensive computation
return computedValue;
}, [dependencies]);
// Stable callbacks
const stableCallback = useCallback(() => {
// Callback logic
}, [dependencies]);
// Effects
useEffect(() => {
// Effect logic
// Cleanup
return () => {
mountedRef.current = false;
// Additional cleanup
};
}, [dependencies]);
return {
// Return values
};
}
````
2. **TypeScript Type Definitions:**
```typescript
// Hook parameters type
interface Use[HookName]Params {
param1: string;
param2?: number;
onSuccess?: (data: Data) => void;
onError?: (error: Error) => void;
}
// Hook return type
interface Use[HookName]Return<T> {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
reset: () => void;
}
// Internal state type
interface HookState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
```
3. **Common Hook Patterns:**
**Data Fetching Hook:**
```typescript
export function useFetch<T>(
url: string,
options?: RequestInit
): {
data: T | null;
loading: boolean;
error: Error | null;
refetch: () => void;
} {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(response.statusText);
const json = await response.json();
setData(json);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}, [url, options]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
```
**Debounce Hook:**
```typescript
export function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
```
**Local Storage Hook:**
```typescript
export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = useCallback(
(value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
},
[key, storedValue]
);
return [storedValue, setValue];
}
```
**Intersection Observer Hook:**
```typescript
export function useIntersectionObserver(
ref: RefObject<Element>,
options?: IntersectionObserverInit
): IntersectionObserverEntry | null {
const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(([entry]) => setEntry(entry), options);
observer.observe(element);
return () => {
observer.disconnect();
};
}, [ref, options]);
return entry;
}
```
**Previous Value Hook:**
```typescript
export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
```
4. **Dependency Management:**
**Correct Dependencies:**
```typescript
// ✅ Correct: All dependencies listed
useEffect(() => {
fetchData(userId, filter);
}, [userId, filter]);
// ✅ Correct: Stable callback with useCallback
const handleClick = useCallback(() => {
doSomething(value);
}, [value]);
// ❌ Wrong: Missing dependencies
useEffect(() => {
fetchData(userId, filter);
}, []); // Missing userId and filter!
// ❌ Wrong: Unnecessary dependencies
useEffect(() => {
console.log('Mounted');
}, [someValue]); // someValue not used!
```
**Stable References:**
```typescript
// Use ref for values that shouldn't trigger re-renders
const callbackRef = useRef(callback);
useEffect(() => {
callbackRef.current = callback;
}, [callback]);
useEffect(() => {
// Use callbackRef.current instead of callback
callbackRef.current();
}, []); // Empty deps array is now safe
```
5. **Cleanup Patterns:**
**Async Cleanup:**
```typescript
useEffect(() => {
let cancelled = false;
async function fetchData() {
const data = await api.fetch();
if (!cancelled) {
setData(data);
}
}
fetchData();
return () => {
cancelled = true;
};
}, []);
```
**Subscription Cleanup:**
```typescript
useEffect(() => {
const subscription = observable.subscribe((data) => {
setData(data);
});
return () => {
subscription.unsubscribe();
};
}, [observable]);
```
**Timer Cleanup:**
```typescript
useEffect(() => {
const interval = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
```
6. **Performance Optimization:**
**Memoization:**
```typescript
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
```
**Callback Stability:**
```typescript
const handleSubmit = useCallback((data: FormData) => {
api.submit(data);
}, []); // Stable reference
```
7. **Error Handling:**
```typescript
export function useSafeAsync<T>() {
const mountedRef = useRef(true);
useEffect(() => {
return () => {
mountedRef.current = false;
};
}, []);
const safeSetState = useCallback((setState: any) => {
return (...args: any[]) => {
if (mountedRef.current) {
setState(...args);
}
};
}, []);
return { safeSetState };
}
```
8. **Testing Strategy:**
```typescript
import { renderHook, act } from '@testing-library/react';
import { use[HookName] } from './use[HookName]';
describe('use[HookName]', () => {
it('should initialize with correct values', () => {
const { result } = renderHook(() => use[HookName](params));
expect(result.current.data).toBeNull();
expect(result.current.loading).toBe(true);
});
it('should handle updates correctly', async () => {
const { result, waitForNextUpdate } = renderHook(() =>
use[HookName](params)
);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
expect(result.current.data).toBeDefined();
});
it('should cleanup on unmount', () => {
const { unmount } = renderHook(() => use[HookName](params));
unmount();
// Verify cleanup occurred
});
});
```
9. **Usage Examples:**
```typescript
function MyComponent() {
const { data, loading, error, refetch } = use[HookName]({
param1: 'value',
param2: 123,
onSuccess: (data) => console.log('Success:', data),
onError: (error) => console.error('Error:', error)
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button onClick={refetch}>Refetch</button>
</div>
);
}
```
10. **Documentation:**
````typescript
/**
* Custom hook for [PURPOSE]
*
* @param {[TYPE]} [PARAM] - [DESCRIPTION]
* @returns {Object} Hook return value
* @returns {[TYPE]} return.data - [DESCRIPTION]
* @returns {boolean} return.loading - [DESCRIPTION]
* @returns {Error | null} return.error - [DESCRIPTION]
*
* @example
* ```tsx
* const { data, loading } = use[HookName]('param');
* ```
*/
````
11. **Common Pitfalls to Avoid:**
- Infinite loops from missing dependencies
- Stale closures
- Memory leaks from missing cleanup
- Unnecessary re-renders
- Race conditions in async operations
Provide the complete custom hook with proper TypeScript types, dependency management, cleanup, error handling, tests, and usage examples. Include comments explaining complex logic and potential pitfalls.