react-hook-builder
Creates custom React hooks for common patterns including data fetching, forms, authentication, local storage, debounce, and more. Use when users request "create custom hook", "React hook for", "useX hook", or "reusable hook".
What this skill does
# React Hook Builder
Build production-ready custom React hooks following best practices and TypeScript patterns.
## Core Workflow
1. **Identify the pattern**: Determine what logic to encapsulate
2. **Design the API**: Define inputs, outputs, and options
3. **Add TypeScript types**: Full type safety with generics
4. **Handle edge cases**: Loading, errors, cleanup
5. **Optimize performance**: Memoization where needed
6. **Write tests**: Cover all states and scenarios
## Hook Naming Conventions
```typescript
// Always prefix with "use"
useLocalStorage // ✓
useDebounce // ✓
useFetch // ✓
localStorageHook // ✗
fetchData // ✗
```
## Data Fetching Hooks
### useFetch
```typescript
// hooks/useFetch.ts
import { useState, useEffect, useCallback, useRef } from 'react';
interface UseFetchOptions<T> {
immediate?: boolean;
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
}
interface UseFetchResult<T> {
data: T | null;
error: Error | null;
isLoading: boolean;
isError: boolean;
isSuccess: boolean;
refetch: () => Promise<void>;
}
export function useFetch<T>(
url: string | null,
options: UseFetchOptions<T> = {}
): UseFetchResult<T> {
const { immediate = true, onSuccess, onError } = options;
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null);
const fetchData = useCallback(async () => {
if (!url) return;
// Cancel previous request
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();
setIsLoading(true);
setError(null);
try {
const response = await fetch(url, {
signal: abortControllerRef.current.signal,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
onSuccess?.(result);
} catch (err) {
if (err instanceof Error && err.name === 'AbortError') {
return; // Ignore abort errors
}
const error = err instanceof Error ? err : new Error('Unknown error');
setError(error);
onError?.(error);
} finally {
setIsLoading(false);
}
}, [url, onSuccess, onError]);
useEffect(() => {
if (immediate) {
fetchData();
}
return () => {
abortControllerRef.current?.abort();
};
}, [fetchData, immediate]);
return {
data,
error,
isLoading,
isError: !!error,
isSuccess: !!data && !error,
refetch: fetchData,
};
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useFetch<User>(
`/api/users/${userId}`
);
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <Profile user={user!} />;
}
```
### useMutation
```typescript
// hooks/useMutation.ts
import { useState, useCallback } from 'react';
interface UseMutationOptions<TData, TVariables> {
onSuccess?: (data: TData, variables: TVariables) => void;
onError?: (error: Error, variables: TVariables) => void;
onSettled?: (data: TData | undefined, error: Error | null, variables: TVariables) => void;
}
interface UseMutationResult<TData, TVariables> {
data: TData | null;
error: Error | null;
isLoading: boolean;
isError: boolean;
isSuccess: boolean;
mutate: (variables: TVariables) => Promise<TData | undefined>;
reset: () => void;
}
export function useMutation<TData, TVariables>(
mutationFn: (variables: TVariables) => Promise<TData>,
options: UseMutationOptions<TData, TVariables> = {}
): UseMutationResult<TData, TVariables> {
const { onSuccess, onError, onSettled } = options;
const [data, setData] = useState<TData | null>(null);
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false);
const mutate = useCallback(
async (variables: TVariables) => {
setIsLoading(true);
setError(null);
try {
const result = await mutationFn(variables);
setData(result);
onSuccess?.(result, variables);
onSettled?.(result, null, variables);
return result;
} catch (err) {
const error = err instanceof Error ? err : new Error('Unknown error');
setError(error);
onError?.(error, variables);
onSettled?.(undefined, error, variables);
return undefined;
} finally {
setIsLoading(false);
}
},
[mutationFn, onSuccess, onError, onSettled]
);
const reset = useCallback(() => {
setData(null);
setError(null);
setIsLoading(false);
}, []);
return {
data,
error,
isLoading,
isError: !!error,
isSuccess: !!data && !error,
mutate,
reset,
};
}
// Usage
function CreateUser() {
const { mutate, isLoading } = useMutation(
async (data: CreateUserDto) => {
const res = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data),
});
return res.json();
},
{
onSuccess: () => toast.success('User created!'),
}
);
return (
<button onClick={() => mutate({ name: 'John' })} disabled={isLoading}>
Create User
</button>
);
}
```
## Form Hooks
### useForm
```typescript
// hooks/useForm.ts
import { useState, useCallback, ChangeEvent, FormEvent } from 'react';
type ValidationRules<T> = {
[K in keyof T]?: (value: T[K], values: T) => string | undefined;
};
interface UseFormOptions<T> {
initialValues: T;
validate?: ValidationRules<T>;
onSubmit: (values: T) => void | Promise<void>;
}
interface UseFormResult<T> {
values: T;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
isValid: boolean;
handleChange: (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;
handleBlur: (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => void;
handleSubmit: (e: FormEvent) => void;
setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void;
setFieldError: (field: keyof T, error: string) => void;
reset: () => void;
}
export function useForm<T extends Record<string, any>>({
initialValues,
validate = {},
onSubmit,
}: UseFormOptions<T>): UseFormResult<T> {
const [values, setValues] = useState<T>(initialValues);
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const validateField = useCallback(
(name: keyof T, value: T[keyof T]) => {
const validator = validate[name];
if (validator) {
return validator(value, values);
}
return undefined;
},
[validate, values]
);
const validateAll = useCallback(() => {
const newErrors: Partial<Record<keyof T, string>> = {};
let isValid = true;
(Object.keys(values) as Array<keyof T>).forEach((key) => {
const error = validateField(key, values[key]);
if (error) {
newErrors[key] = error;
isValid = false;
}
});
setErrors(newErrors);
return isValid;
}, [values, validateField]);
const handleChange = useCallback(
(e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;
const newValue = type === 'checkbox' ? (e.target as HTMLInputElement).checked : value;
setValues((prev) => ({ ...prev, [name]: newValue }));
// Clear error on change
if (errors[name as keyof T]) {
setErrors((prev) => ({ ...prev, [name]: undefined }));
}
},
[errors]
);
const handleBlur = useCallback(
(e: ChangeEvent<HTMLInputElement |Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.