268 lines
6.6 KiB
TypeScript
268 lines
6.6 KiB
TypeScript
/**
|
|
* Custom Hook Template
|
|
*
|
|
* Usage: Copy this file to create new custom hooks
|
|
* 1. Replace TEMPLATE_NAME with your hook name (use camelCase)
|
|
* 2. Define the hook parameters and return type
|
|
* 3. Implement your hook logic
|
|
*/
|
|
|
|
'use client'
|
|
|
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
|
// Hook parameters interface
|
|
interface UseTemplateHookParams {
|
|
// Define your parameters here
|
|
initialValue?: string
|
|
autoFetch?: boolean
|
|
onSuccess?: (data: any) => void
|
|
onError?: (error: Error) => void
|
|
debounceMs?: number
|
|
}
|
|
|
|
// Hook return type
|
|
interface UseTemplateHookReturn {
|
|
// Define what your hook returns
|
|
data: any
|
|
isLoading: boolean
|
|
error: Error | null
|
|
refetch: () => void
|
|
mutate: (data: any) => void
|
|
reset: () => void
|
|
}
|
|
|
|
/**
|
|
* Custom hook template
|
|
*
|
|
* @param params - Hook parameters
|
|
* @returns Hook state and functions
|
|
*/
|
|
export function useTemplateHook({
|
|
initialValue = '',
|
|
autoFetch = true,
|
|
onSuccess,
|
|
onError,
|
|
debounceMs = 300,
|
|
}: UseTemplateHookParams = {}): UseTemplateHookReturn {
|
|
// Local state
|
|
const [data, setData] = useState<any>(initialValue)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [error, setError] = useState<Error | null>(null)
|
|
|
|
// Refs for cleanup and debouncing
|
|
const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined)
|
|
const queryClient = useQueryClient()
|
|
|
|
// Query for fetching data
|
|
const query = useQuery({
|
|
queryKey: ['template-hook', initialValue],
|
|
queryFn: async () => {
|
|
// Implement your data fetching logic
|
|
const response = await fetch('/api/template-endpoint', {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`)
|
|
}
|
|
|
|
return response.json()
|
|
},
|
|
enabled: autoFetch,
|
|
})
|
|
|
|
// Handle query success/error with useEffect
|
|
useEffect(() => {
|
|
if (query.data && !query.isLoading && !query.error) {
|
|
setData(query.data)
|
|
onSuccess?.(query.data)
|
|
}
|
|
}, [query.data, query.isLoading, query.error, onSuccess])
|
|
|
|
useEffect(() => {
|
|
if (query.error) {
|
|
setError(query.error)
|
|
onError?.(query.error)
|
|
}
|
|
}, [query.error, onError])
|
|
|
|
// Mutation for updating data
|
|
const mutation = useMutation({
|
|
mutationFn: async (newData: any) => {
|
|
const response = await fetch('/api/template-endpoint', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(newData),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`)
|
|
}
|
|
|
|
return response.json()
|
|
},
|
|
})
|
|
|
|
// Handle mutation success/error with useEffect
|
|
useEffect(() => {
|
|
if (mutation.data && mutation.isSuccess) {
|
|
// Update cache
|
|
queryClient.setQueryData(['template-hook', initialValue], mutation.data)
|
|
setData(mutation.data)
|
|
onSuccess?.(mutation.data)
|
|
}
|
|
}, [mutation.data, mutation.isSuccess, queryClient, initialValue, onSuccess])
|
|
|
|
useEffect(() => {
|
|
if (mutation.error) {
|
|
setError(mutation.error)
|
|
onError?.(mutation.error)
|
|
}
|
|
}, [mutation.error, onError])
|
|
|
|
// Debounced function
|
|
const debouncedFunction = useCallback(
|
|
(value: any) => {
|
|
if (timeoutRef.current) {
|
|
clearTimeout(timeoutRef.current)
|
|
}
|
|
|
|
timeoutRef.current = setTimeout(() => {
|
|
// Implement debounced logic here
|
|
console.log('Debounced value:', value)
|
|
}, debounceMs)
|
|
},
|
|
[debounceMs]
|
|
)
|
|
|
|
// Effect for cleanup
|
|
useEffect(() => {
|
|
return () => {
|
|
if (timeoutRef.current) {
|
|
clearTimeout(timeoutRef.current)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
// Refetch function
|
|
const refetch = useCallback(() => {
|
|
query.refetch()
|
|
}, [query])
|
|
|
|
// Mutate function
|
|
const mutate = useCallback(
|
|
(newData: any) => {
|
|
mutation.mutate(newData)
|
|
},
|
|
[mutation]
|
|
)
|
|
|
|
// Reset function
|
|
const reset = useCallback(() => {
|
|
setData(initialValue)
|
|
setError(null)
|
|
queryClient.removeQueries({ queryKey: ['template-hook', initialValue] })
|
|
}, [initialValue, queryClient])
|
|
|
|
return {
|
|
data: query.data || data,
|
|
isLoading: query.isLoading || mutation.isPending,
|
|
error: query.error || mutation.error || error,
|
|
refetch,
|
|
mutate,
|
|
reset,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Alternative hook patterns:
|
|
*
|
|
* 1. Simple State Hook:
|
|
*
|
|
* export function useSimpleState<T>(initialValue: T) {
|
|
* const [value, setValue] = useState<T>(initialValue);
|
|
*
|
|
* const reset = useCallback(() => setValue(initialValue), [initialValue]);
|
|
*
|
|
* return [value, setValue, reset] as const;
|
|
* }
|
|
*
|
|
* 2. Local Storage Hook:
|
|
*
|
|
* export function useLocalStorage<T>(key: string, initialValue: T) {
|
|
* const [storedValue, setStoredValue] = useState<T>(() => {
|
|
* try {
|
|
* const item = window.localStorage.getItem(key);
|
|
* return item ? JSON.parse(item) : initialValue;
|
|
* } catch (error) {
|
|
* return initialValue;
|
|
* }
|
|
* });
|
|
*
|
|
* const setValue = (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 saving to localStorage:', error);
|
|
* }
|
|
* };
|
|
*
|
|
* return [storedValue, setValue] as const;
|
|
* }
|
|
*
|
|
* 3. API Hook with SWR pattern:
|
|
*
|
|
* export function useApi<T>(url: string, options?: RequestInit) {
|
|
* const [data, setData] = useState<T | null>(null);
|
|
* const [loading, setLoading] = useState(true);
|
|
* const [error, setError] = useState<Error | null>(null);
|
|
*
|
|
* useEffect(() => {
|
|
* let cancelled = false;
|
|
*
|
|
* const fetchData = async () => {
|
|
* try {
|
|
* setLoading(true);
|
|
* const response = await fetch(url, options);
|
|
*
|
|
* if (!response.ok) {
|
|
* throw new Error(`HTTP error! status: ${response.status}`);
|
|
* }
|
|
*
|
|
* const result = await response.json();
|
|
*
|
|
* if (!cancelled) {
|
|
* setData(result);
|
|
* setError(null);
|
|
* }
|
|
* } catch (err) {
|
|
* if (!cancelled) {
|
|
* setError(err instanceof Error ? err : new Error('Unknown error'));
|
|
* }
|
|
* } finally {
|
|
* if (!cancelled) {
|
|
* setLoading(false);
|
|
* }
|
|
* }
|
|
* };
|
|
*
|
|
* fetchData();
|
|
*
|
|
* return () => {
|
|
* cancelled = true;
|
|
* };
|
|
* }, [url]);
|
|
*
|
|
* return { data, loading, error };
|
|
* }
|
|
*/
|