Claude-skill-registry hookify
Hook creation and management system for React, Vue, and other frameworks with automated hook generation, testing, and documentation.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hookify" ~/.claude/skills/majiayu000-claude-skill-registry-hookify && rm -rf "$T"
manifest:
skills/data/hookify/SKILL.mdsource content
Hook Creation & Management System
Overview
Comprehensive hook development toolkit providing automated hook generation, testing utilities, documentation generation, and management for React, Vue, and other modern frontend frameworks.
Quick Start
Installation
npm install -g @hookify/cli # or npx @hookify/cli init
Initialize Hook Project
# Initialize in existing project hookify init # Create new hook library hookify create my-hooks --template=react # Add to existing project hookify add --project=my-react-app --framework=react
Hook Generation
React Hooks
# Generate custom hook hookify generate useUserData --framework=react # Generate with dependencies hookify generate useApi --framework=react --deps=useState,useEffect # Generate with TypeScript hookify generate useLocalStorage --framework=react --typescript --generic
React Hook Template
// hooks/useApi.ts import { useState, useEffect, useCallback } from 'react'; interface UseApiOptions<T> { immediate?: boolean; onSuccess?: (data: T) => void; onError?: (error: Error) => void; } interface UseApiReturn<T> { data: T | null; loading: boolean; error: Error | null; execute: () => Promise<void>; reset: () => void; } export function useApi<T>( url: string, options: UseApiOptions<T> = {} ): UseApiReturn<T> { const { immediate = true, onSuccess, onError } = options; const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState<Error | null>(null); const execute = useCallback(async () => { try { setLoading(true); setError(null); const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); setData(result); onSuccess?.(result); } catch (err) { const error = err instanceof Error ? err : new Error('Unknown error'); setError(error); onError?.(error); } finally { setLoading(false); } }, [url, onSuccess, onError]); const reset = useCallback(() => { setData(null); setLoading(false); setError(null); }, []); useEffect(() => { if (immediate) { execute(); } }, [immediate, execute]); return { data, loading, error, execute, reset }; }
Vue Composition API
# Generate Vue composable hookify generate useUserData --framework=vue # Generate with reactivity hookify generate useCounter --framework=vue --reactive # Generate with TypeScript hookify generate useLocalStorage --framework=vue --typescript
Vue Composable Template
// composables/useApi.ts import { ref, computed, watch } from 'vue'; interface UseApiOptions<T> { immediate?: boolean; onSuccess?: (data: T) => void; onError?: (error: Error) => void; } export function useApi<T>( url: string, options: UseApiOptions<T> = {} ) { const { immediate = true, onSuccess, onError } = options; const data = ref<T | null>(null); const loading = ref(false); const error = ref<Error | null>(null); const execute = async () => { try { loading.value = true; error.value = null; const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); data.value = result; onSuccess?.(result); } catch (err) { const errorValue = err instanceof Error ? err : new Error('Unknown error'); error.value = errorValue; onError?.(errorValue); } finally { loading.value = false; } }; const reset = () => { data.value = null; loading.value = false; error.value = null; }; // Computed properties const isIdle = computed(() => !loading.value && !error.value && data.value === null); const isSuccess = computed(() => !loading.value && !error.value && data.value !== null); const isError = computed(() => !loading.value && error.value !== null); // Auto-execute if immediate if (immediate) { execute(); } return { data, loading: readonly(loading), error: readonly(error), execute, reset, isIdle, isSuccess, isError }; }
Svelte Hooks
# Generate Svelte store hookify generate useUserData --framework=svelte # Generate writable store hookify generate useCounter --framework=svelte --type=writable # Generate derived store hookify generate useFilteredData --framework=svelte --type=derived
Svelte Store Template
// stores/useApi.ts import { writable, derived } from 'svelte/store'; interface ApiState<T> { data: T | null; loading: boolean; error: Error | null; } function createApiStore<T>(url: string) { const { subscribe, set, update } = writable<ApiState<T>>({ data: null, loading: false, error: null }); const execute = async () => { update(state => ({ ...state, loading: true, error: null })); try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); set({ data: result, loading: false, error: null }); } catch (err) { const error = err instanceof Error ? err : new Error('Unknown error'); set({ data: null, loading: false, error }); } }; const reset = () => { set({ data: null, loading: false, error: null }); }; // Derived stores const isIdle = derived(store, $store => !$store.loading && !$store.error && $store.data === null); const isSuccess = derived(store, $store => !$store.loading && !$store.error && $store.data !== null); const isError = derived(store, $store => !$store.loading && $store.error !== null); return { subscribe, execute, reset, isIdle, isSuccess, isError }; } export const useApi = createApiStore;
Hook Templates
Common Hook Patterns
# Generate data fetching hook hookify generate useFetch --template=data-fetching # Generate form handling hook hookify generate useForm --template=form-handling # Generate authentication hook hookify generate useAuth --template=authentication # Generate local storage hook hookify generate useLocalStorage --template=storage
Data Fetching Template
// templates/data-fetching.hook.ts export const dataFetchingTemplate = ` import { useState, useEffect, useCallback, useRef } from 'react'; interface Use{{Name}}Options<T> { immediate?: boolean; cache?: boolean; cacheTime?: number; retry?: number; retryDelay?: number; onSuccess?: (data: T) => void; onError?: (error: Error) => void; } interface Use{{Name}}Return<T> { data: T | null; loading: boolean; error: Error | null; execute: () => Promise<void>; refetch: () => Promise<void>; reset: () => void; } export function use{{Name}}<T>( fetcher: () => Promise<T>, options: Use{{Name}}Options<T> = {} ): Use{{Name}}Return<T> { const { immediate = true, cache = false, cacheTime = 300000, // 5 minutes retry = 3, retryDelay = 1000, onSuccess, onError } = options; const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState<Error | null>(null); const cacheRef = useRef<Map<string, { data: T; timestamp: number }>>(new Map()); const retryCountRef = useRef(0); const execute = useCallback(async () => { try { setLoading(true); setError(null); // Check cache if (cache) { const cacheKey = fetcher.toString(); const cached = cacheRef.current.get(cacheKey); if (cached && Date.now() - cached.timestamp < cacheTime) { setData(cached.data); onSuccess?.(cached.data); return; } } const result = await fetcher(); setData(result); // Update cache if (cache) { const cacheKey = fetcher.toString(); cacheRef.current.set(cacheKey, { data: result, timestamp: Date.now() }); } retryCountRef.current = 0; onSuccess?.(result); } catch (err) { const error = err instanceof Error ? err : new Error('Unknown error'); // Retry logic if (retryCountRef.current < retry) { retryCountRef.current++; setTimeout(execute, retryDelay * retryCountRef.current); return; } setError(error); onError?.(error); } finally { setLoading(false); } }, [fetcher, cache, cacheTime, retry, retryDelay, onSuccess, onError]); const refetch = useCallback(() => { retryCountRef.current = 0; return execute(); }, [execute]); const reset = useCallback(() => { setData(null); setLoading(false); setError(null); retryCountRef.current = 0; }, []); useEffect(() => { if (immediate) { execute(); } }, [immediate, execute]); return { data, loading, error, execute, refetch, reset }; } `;
Form Handling Template
// templates/form-handling.hook.ts export const formHandlingTemplate = ` import { useState, useCallback, useEffect } from 'react'; interface FieldValidation { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp; custom?: (value: any) => string | null; } interface FormField<T = any> { value: T; error: string | null; touched: boolean; validation?: FieldValidation; } interface UseFormOptions<T> { initialValues: T; validation?: Partial<Record<keyof T, FieldValidation>>; onSubmit?: (values: T) => void | Promise<void>; validateOnChange?: boolean; } interface UseFormReturn<T> { values: T; errors: Partial<Record<keyof T, string | null>>; touched: Partial<Record<keyof T, boolean>>; isValid: boolean; isDirty: boolean; isSubmitting: boolean; setValue: <K extends keyof T>(field: K, value: T[K]) => void; setError: <K extends keyof T>(field: K, error: string | null) => void; setTouched: <K extends keyof T>(field: K, touched: boolean) => void; validateField: <K extends keyof T>(field: K) => string | null; validateForm: () => boolean; handleSubmit: () => Promise<void>; resetForm: () => void; resetField: <K extends keyof T>(field: K) => void; } export function useForm<T extends Record<string, any>>( options: UseFormOptions<T> ): UseFormReturn<T> { const { initialValues, validation = {}, onSubmit, validateOnChange = true } = options; const [values, setValues] = useState<T>(initialValues); const [errors, setErrors] = useState<Partial<Record<keyof T, string | null>>>({}); const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({}); const [isSubmitting, setIsSubmitting] = useState(false); const validateField = useCallback(<K extends keyof T>(field: K): string | null => { const value = values[field]; const fieldValidation = validation[field]; if (!fieldValidation) return null; // Required validation if (fieldValidation.required && (!value || value === '')) { return 'This field is required'; } // Length validation if (typeof value === 'string') { if (fieldValidation.minLength && value.length < fieldValidation.minLength) { return \`Minimum length is \${fieldValidation.minLength} characters\`; } if (fieldValidation.maxLength && value.length > fieldValidation.maxLength) { return \`Maximum length is \${fieldValidation.maxLength} characters\`; } } // Pattern validation if (fieldValidation.pattern && typeof value === 'string') { if (!fieldValidation.pattern.test(value)) { return 'Invalid format'; } } // Custom validation if (fieldValidation.custom) { return fieldValidation.custom(value); } return null; }, [values, validation]); const validateForm = useCallback((): boolean => { const newErrors: Partial<Record<keyof T, string | null>> = {}; let isValid = true; Object.keys(validation).forEach((field) => { const error = validateField(field as keyof T); newErrors[field as keyof T] = error; if (error) isValid = false; }); setErrors(newErrors); return isValid; }, [validateField, validation]); const setValue = useCallback(<K extends keyof T>(field: K, value: T[K]) => { setValues(prev => ({ ...prev, [field]: value })); if (validateOnChange) { const error = validateField(field); setErrors(prev => ({ ...prev, [field]: error })); } }, [validateField, validateOnChange]); const setError = useCallback(<K extends keyof T>(field: K, error: string | null) => { setErrors(prev => ({ ...prev, [field]: error })); }, []); const setTouched = useCallback(<K extends keyof T>(field: K, touchedValue: boolean) => { setTouched(prev => ({ ...prev, [field]: touchedValue })); }, []); const handleSubmit = useCallback(async () => { // Validate all fields const isValid = validateForm(); if (!isValid) return; setIsSubmitting(true); try { await onSubmit?.(values); } catch (error) { console.error('Form submission error:', error); } finally { setIsSubmitting(false); } }, [validateForm, onSubmit, values]); const resetForm = useCallback(() => { setValues(initialValues); setErrors({}); setTouched({}); setIsSubmitting(false); }, [initialValues]); const resetField = useCallback(<K extends keyof T>(field: K) => { setValues(prev => ({ ...prev, [field]: initialValues[field] })); setErrors(prev => ({ ...prev, [field]: null })); setTouched(prev => ({ ...prev, [field]: false })); }, [initialValues]); // Computed values const isValid = Object.values(errors).every(error => !error); const isDirty = Object.keys(touched).some(key => touched[key as keyof T]); return { values, errors, touched, isValid, isDirty, isSubmitting, setValue, setError, setTouched, validateField, validateForm, handleSubmit, resetForm, resetField }; } `;
Hook Testing
Test Generation
# Generate hook tests hookify test generate useApi --framework=react-testing-library # Generate test with custom scenarios hookify test generate useLocalStorage --scenarios=basic,error,edge-cases # Generate performance tests hookify test generate useApi --performance --memory-leaks
Hook Test Template
// test/hooks/useApi.test.ts import { renderHook, act, waitFor } from '@testing-library/react'; import { useApi } from '../hooks/useApi'; // Mock fetch global.fetch = jest.fn(); describe('useApi', () => { beforeEach(() => { jest.clearAllMocks(); }); test('should initialize with loading state', () => { const { result } = renderHook(() => useApi('https://api.example.com/data')); expect(result.current.loading).toBe(true); expect(result.current.data).toBe(null); expect(result.current.error).toBe(null); }); test('should fetch data successfully', async () => { const mockData = { id: 1, name: 'Test Data' }; (fetch as jest.Mock).mockResolvedValueOnce({ ok: true, json: async () => mockData }); const { result } = renderHook(() => useApi('https://api.example.com/data')); await waitFor(() => { expect(result.current.loading).toBe(false); expect(result.current.data).toEqual(mockData); expect(result.current.error).toBe(null); }); expect(fetch).toHaveBeenCalledWith('https://api.example.com/data'); }); test('should handle fetch error', async () => { const mockError = new Error('Network error'); (fetch as jest.Mock).mockRejectedValueOnce(mockError); const onError = jest.fn(); const { result } = renderHook(() => useApi('https://api.example.com/data', { onError }) ); await waitFor(() => { expect(result.current.loading).toBe(false); expect(result.current.data).toBe(null); expect(result.current.error).toEqual(mockError); }); expect(onError).toHaveBeenCalledWith(mockError); }); test('should not fetch immediately when immediate is false', () => { const { result } = renderHook(() => useApi('https://api.example.com/data', { immediate: false }) ); expect(result.current.loading).toBe(false); expect(fetch).not.toHaveBeenCalled(); }); test('should execute manual refetch', async () => { const mockData = { id: 1, name: 'Test Data' }; (fetch as jest.Mock).mockResolvedValueOnce({ ok: true, json: async () => mockData }); const { result } = renderHook(() => useApi('https://api.example.com/data', { immediate: false }) ); act(() => { result.current.execute(); }); await waitFor(() => { expect(result.current.data).toEqual(mockData); }); expect(fetch).toHaveBeenCalledTimes(1); }); test('should reset state', () => { const { result } = renderHook(() => useApi('https://api.example.com/data', { immediate: false }) ); // Simulate some state change act(() => { result.current.reset(); }); expect(result.current.data).toBe(null); expect(result.current.loading).toBe(false); expect(result.current.error).toBe(null); }); });
Performance Testing
// test/performance/useApi.performance.test.ts import { renderHook, act } from '@testing-library/react'; import { useApi } from '../hooks/useApi'; describe('useApi Performance', () => { test('should not cause memory leaks', () => { const { unmount } = renderHook(() => useApi('https://api.example.com/data')); // Force garbage collection if available if (global.gc) { global.gc(); } // Unmount hook unmount(); // Check for memory leaks (implementation depends on your setup) // This is a placeholder for actual memory leak detection expect(true).toBe(true); }); test('should handle rapid state updates efficiently', async () => { const startTime = performance.now(); const { result } = renderHook(() => useApi('https://api.example.com/data')); // Simulate rapid calls for (let i = 0; i < 100; i++) { act(() => { result.current.execute(); }); } const endTime = performance.now(); const duration = endTime - startTime; // Should complete within reasonable time (adjust threshold as needed) expect(duration).toBeLessThan(1000); // 1 second }); });
Hook Documentation
Auto-Documentation
# Generate hook documentation hookify docs generate useApi --format=markdown # Generate Storybook stories hookify docs storybook useApi --framework=react # Generate API reference hookify docs api --output=./docs/hooks
Documentation Template
# useApi A custom hook for handling API calls with loading states, error handling, and caching capabilities. ## Usage \`\`\`typescript import { useApi } from './hooks/useApi'; interface User { id: number; name: string; email: string; } function UserProfile({ userId }: { userId: number }) { const { data: user, loading, error, execute } = useApi<User>( \`https://api.example.com/users/\${userId}\` ); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!user) return <div>No user found</div>; return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> <button onClick={execute}>Refresh</button> </div> ); } \`\`\` ## API ### Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | url | \`string\` | - | The API endpoint URL | | options | \`UseApiOptions<T>\` | \`{}\` | Configuration options | ### Options | Option | Type | Default | Description | |--------|------|---------|-------------| | immediate | \`boolean\` | \`true\` | Whether to fetch data immediately | | onSuccess | \`(data: T) => void\` | - | Callback called on successful fetch | | onError | \`(error: Error) => void\` | - | Callback called on error | ### Return Value | Property | Type | Description | |----------|------|-------------| | data | \`T | null\` | The fetched data | | loading | \`boolean\` | Whether the request is in progress | | error | \`Error | null\` | Any error that occurred | | execute | \`() => Promise<void>\` | Function to manually trigger the request | | reset | \`() => void\` | Function to reset the hook state | ## Examples ### Basic Usage \`\`\`typescript const { data, loading, error } = useApi('https://api.example.com/data'); \`\`\` ### With Error Handling \`\`\`typescript const { data, loading, error } = useApi('https://api.example.com/data', { onError: (error) => console.error('API Error:', error) }); \`\`\` ### Manual Execution \`\`\`typescript const { data, loading, error, execute } = useApi( 'https://api.example.com/data', { immediate: false } ); // Trigger request manually const handleRefresh = () => { execute(); }; \`\`\` ## TypeScript Support This hook is fully typed with TypeScript: \`\`\`typescript interface ApiResponse { id: number; name: string; } const { data } = useApi<ApiResponse>('https://api.example.com/data'); // data is typed as ApiResponse | null \`\`\` ## Dependencies - React 16.8+ (for hooks support) - TypeScript 4.0+ (for type support) ## Related Hooks - \`useFetch\` - Simpler data fetching hook - \`useLocalStorage\` - Local storage synchronization - \`useDebounce\` - Debounced values
Hook Registry
Hook Management
# List all hooks hookify list # Search hooks hookify search --keyword=api # Get hook info hookify info useApi # Install hook from registry hookify install useAuth --registry=@company/hooks
Hook Registry Configuration
// hookify.config.js module.exports = { registry: { default: 'https://registry.hookify.dev', private: 'https://hooks.company.com', local: './hooks' }, hooks: { // Local hooks './hooks': { pattern: '**/*.hook.{js,ts}', autoRegister: true }, // Registry hooks '@company/hooks': { version: '^1.0.0', autoUpdate: true } }, validation: { typescript: true, tests: true, documentation: true, performance: true }, publishing: { registry: 'https://registry.hookify.dev', autoVersion: true, changelog: true } };
Hook Publishing
# Publish hook to registry hookify publish useApi --registry=public # Publish with version hookify publish useAuth --version=2.1.0 # Publish to private registry hookify publish useCompanyData --registry=@company/hooks
Integration
Framework Integration
# React integration hookify integrate react --typescript=true # Vue integration hookify integrate vue --composition-api # Svelte integration hookify integrate svelte --typescript=true
Build Integration
// webpack.config.js const { HookifyPlugin } = require('@hookify/webpack'); module.exports = { plugins: [ new HookifyPlugin({ hooks: './src/hooks', output: './dist/hooks', optimization: { treeShaking: true, minify: true, bundleAnalysis: true } }) ] };
API Reference
Core Classes
HookGenerator
import { HookGenerator } from '@hookify/core'; const generator = new HookGenerator({ framework: 'react', typescript: true, testing: true }); const hook = await generator.generate('useApi', { template: 'data-fetching', dependencies: ['useState', 'useEffect'] });
HookTester
import { HookTester } from '@hookify/testing'; const tester = new HookTester({ framework: 'react-testing-library', coverage: true }); const testResults = await tester.test('useApi');
HookRegistry
import { HookRegistry } from '@hookify/registry'; const registry = new HookRegistry({ endpoint: 'https://registry.hookify.dev' }); const hooks = await registry.search('api'); const hook = await registry.get('useApi');
Contributing
- Fork repository
- Create hook feature branch
- Add comprehensive tests
- Generate documentation
- Submit pull request
License
MIT License - see LICENSE file for details.