312 lines
8.2 KiB
TypeScript
312 lines
8.2 KiB
TypeScript
/**
|
|
* Custom React Hook Template
|
|
*
|
|
* This template provides a starting point for creating reusable
|
|
* custom hooks following React best practices.
|
|
*
|
|
* Usage:
|
|
* 1. Copy this template to your hooks directory
|
|
* 2. Rename file and hook
|
|
* 3. Define input parameters and return type
|
|
* 4. Implement hook logic
|
|
* 5. Add tests and documentation
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Options for the useCustomHook
|
|
*/
|
|
export interface UseCustomHookOptions {
|
|
/** Enable/disable the hook */
|
|
enabled?: boolean;
|
|
|
|
/** Callback when data is loaded */
|
|
onSuccess?: (data: string) => void;
|
|
|
|
/** Callback when error occurs */
|
|
onError?: (error: Error) => void;
|
|
|
|
/** Retry count */
|
|
retryCount?: number;
|
|
}
|
|
|
|
/**
|
|
* Return type of useCustomHook
|
|
*/
|
|
export interface UseCustomHookResult {
|
|
/** Current data */
|
|
data: string | null;
|
|
|
|
/** Loading state */
|
|
isLoading: boolean;
|
|
|
|
/** Error state */
|
|
error: Error | null;
|
|
|
|
/** Refetch function */
|
|
refetch: () => void;
|
|
|
|
/** Reset function */
|
|
reset: () => void;
|
|
}
|
|
|
|
// ============================================================================
|
|
// CONSTANTS
|
|
// ============================================================================
|
|
|
|
const DEFAULT_OPTIONS: Required<UseCustomHookOptions> = {
|
|
enabled: true,
|
|
onSuccess: () => {},
|
|
onError: () => {},
|
|
retryCount: 3,
|
|
};
|
|
|
|
// ============================================================================
|
|
// HOOK
|
|
// ============================================================================
|
|
|
|
/**
|
|
* useCustomHook - Brief description of what this hook does
|
|
*
|
|
* More detailed description of the hook's purpose, use cases,
|
|
* and any important implementation notes.
|
|
*
|
|
* @param param1 - Description of param1
|
|
* @param options - Configuration options
|
|
* @returns Hook result with data, loading, error states and utility functions
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* function MyComponent() {
|
|
* const { data, isLoading, error, refetch } = useCustomHook('value', {
|
|
* enabled: true,
|
|
* onSuccess: (data) => console.log('Success:', data),
|
|
* });
|
|
*
|
|
* if (isLoading) return <Spinner />;
|
|
* if (error) return <Error message={error.message} />;
|
|
*
|
|
* return <div>{data}</div>;
|
|
* }
|
|
* ```
|
|
*/
|
|
export function useCustomHook(
|
|
param1: string,
|
|
options: UseCustomHookOptions = {}
|
|
): UseCustomHookResult {
|
|
// ========================================================================
|
|
// OPTIONS
|
|
// ========================================================================
|
|
|
|
const {
|
|
enabled = DEFAULT_OPTIONS.enabled,
|
|
onSuccess = DEFAULT_OPTIONS.onSuccess,
|
|
onError = DEFAULT_OPTIONS.onError,
|
|
retryCount = DEFAULT_OPTIONS.retryCount,
|
|
} = options;
|
|
|
|
// ========================================================================
|
|
// STATE
|
|
// ========================================================================
|
|
|
|
const [data, setData] = useState<string | null>(null);
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
|
|
// ========================================================================
|
|
// REFS
|
|
// ========================================================================
|
|
|
|
// Use refs for values that shouldn't trigger re-renders
|
|
const retryCountRef = useRef(0);
|
|
const mountedRef = useRef(true);
|
|
|
|
// ========================================================================
|
|
// CLEANUP
|
|
// ========================================================================
|
|
|
|
useEffect(() => {
|
|
mountedRef.current = true;
|
|
|
|
return () => {
|
|
mountedRef.current = false;
|
|
};
|
|
}, []);
|
|
|
|
// ========================================================================
|
|
// FETCH LOGIC
|
|
// ========================================================================
|
|
|
|
const fetchData = useCallback(async () => {
|
|
if (!enabled) return;
|
|
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Simulate async operation
|
|
const result = await new Promise<string>((resolve, reject) => {
|
|
setTimeout(() => {
|
|
if (Math.random() > 0.8) {
|
|
reject(new Error('Random error'));
|
|
} else {
|
|
resolve(`Data for ${param1}`);
|
|
}
|
|
}, 1000);
|
|
});
|
|
|
|
// Only update state if component is still mounted
|
|
if (mountedRef.current) {
|
|
setData(result);
|
|
setIsLoading(false);
|
|
onSuccess(result);
|
|
retryCountRef.current = 0; // Reset retry count on success
|
|
}
|
|
} catch (err) {
|
|
if (mountedRef.current) {
|
|
const error = err instanceof Error ? err : new Error('Unknown error');
|
|
|
|
// Retry logic
|
|
if (retryCountRef.current < retryCount) {
|
|
retryCountRef.current++;
|
|
setTimeout(() => fetchData(), 1000 * retryCountRef.current); // Exponential backoff
|
|
} else {
|
|
setError(error);
|
|
setIsLoading(false);
|
|
onError(error);
|
|
}
|
|
}
|
|
}
|
|
}, [enabled, param1, onSuccess, onError, retryCount]);
|
|
|
|
// ========================================================================
|
|
// EFFECTS
|
|
// ========================================================================
|
|
|
|
useEffect(() => {
|
|
if (enabled) {
|
|
fetchData();
|
|
}
|
|
|
|
// Cleanup function
|
|
return () => {
|
|
// Cancel any pending operations
|
|
};
|
|
}, [enabled, fetchData]);
|
|
|
|
// ========================================================================
|
|
// HANDLERS
|
|
// ========================================================================
|
|
|
|
const refetch = useCallback(() => {
|
|
retryCountRef.current = 0; // Reset retry count
|
|
fetchData();
|
|
}, [fetchData]);
|
|
|
|
const reset = useCallback(() => {
|
|
setData(null);
|
|
setError(null);
|
|
setIsLoading(false);
|
|
retryCountRef.current = 0;
|
|
}, []);
|
|
|
|
// ========================================================================
|
|
// RETURN
|
|
// ========================================================================
|
|
|
|
return {
|
|
data,
|
|
isLoading,
|
|
error,
|
|
refetch,
|
|
reset,
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// HELPER HOOKS (Optional)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* useDebounce - Debounce a value
|
|
*
|
|
* Useful for search inputs, window resize handlers, etc.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const [searchTerm, setSearchTerm] = useState('');
|
|
* const debouncedSearchTerm = useDebounce(searchTerm, 500);
|
|
*
|
|
* useEffect(() => {
|
|
* // API call with debounced value
|
|
* searchAPI(debouncedSearchTerm);
|
|
* }, [debouncedSearchTerm]);
|
|
* ```
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* useLocalStorage - Sync state with localStorage
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
* ```
|
|
*/
|
|
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 reading localStorage key "${key}":`, 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 setting localStorage key "${key}":`, error);
|
|
}
|
|
},
|
|
[key, storedValue]
|
|
);
|
|
|
|
return [storedValue, setValue];
|
|
}
|
|
|
|
// ============================================================================
|
|
// EXPORTS
|
|
// ============================================================================
|
|
|
|
export default useCustomHook;
|