From claude-initial-setup
Advanced React hooks patterns including custom hooks, memoization, refs, reducers, and external store synchronization. Use when the user is writing custom hooks, optimizing React performance with useCallback/useMemo, managing complex state with useReducer, or integrating external state stores. Trigger on any mention of React hooks, custom hooks, memoization, re-render optimization, or rules of hooks violations.
npx claudepluginhub versoxbt/claude-initial-setup --plugin claude-initial-setupThis skill uses the workspace's default tool permissions.
Patterns for writing correct, performant, and composable React hooks.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
Patterns for writing correct, performant, and composable React hooks.
Custom hooks encapsulate stateful logic for reuse across components. Name them with the use prefix.
import { useState, useEffect, useRef } from 'react'
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null)
const [error, setError] = useState<Error | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const controller = new AbortController()
setIsLoading(true)
setError(null)
fetch(url, { signal: controller.signal })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
})
.then((json) => setData(json as T))
.catch((err) => {
if (err.name !== 'AbortError') setError(err)
})
.finally(() => setIsLoading(false))
return () => controller.abort()
}, [url])
return { data, error, isLoading }
}
Memoize only when passing callbacks to memoized children or when computation is expensive. Do not memoize everything by default.
import { useCallback, useMemo } from 'react'
function ProductList({ products, onSelect }: Props) {
// Memoize because this is passed to React.memo children
const handleSelect = useCallback(
(id: string) => {
onSelect(id)
},
[onSelect]
)
// Memoize because sorting is O(n log n)
const sorted = useMemo(
() => [...products].sort((a, b) => a.price - b.price),
[products]
)
return (
<ul>
{sorted.map((p) => (
<ProductItem key={p.id} product={p} onSelect={handleSelect} />
))}
</ul>
)
}
const ProductItem = React.memo(({ product, onSelect }: ItemProps) => (
<li onClick={() => onSelect(product.id)}>{product.name}</li>
))
Use refs for values that should persist across renders without triggering re-renders: timers, previous values, DOM elements.
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback)
// Update ref on every render so the interval always calls the latest callback
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
if (delay === null) return
const id = setInterval(() => savedCallback.current(), delay)
return () => clearInterval(id)
}, [delay])
}
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined)
useEffect(() => {
ref.current = value
})
return ref.current
}
Prefer useReducer when state transitions depend on previous state or when multiple sub-values change together.
import { useReducer } from 'react'
interface FormState {
values: Record<string, string>
errors: Record<string, string>
isSubmitting: boolean
}
type FormAction =
| { type: 'SET_FIELD'; field: string; value: string }
| { type: 'SET_ERROR'; field: string; error: string }
| { type: 'SUBMIT_START' }
| { type: 'SUBMIT_SUCCESS' }
| { type: 'SUBMIT_FAILURE'; errors: Record<string, string> }
function formReducer(state: FormState, action: FormAction): FormState {
switch (action.type) {
case 'SET_FIELD':
return {
...state,
values: { ...state.values, [action.field]: action.value },
errors: { ...state.errors, [action.field]: '' },
}
case 'SET_ERROR':
return { ...state, errors: { ...state.errors, [action.field]: action.error } }
case 'SUBMIT_START':
return { ...state, isSubmitting: true }
case 'SUBMIT_SUCCESS':
return { ...state, isSubmitting: false, errors: {} }
case 'SUBMIT_FAILURE':
return { ...state, isSubmitting: false, errors: action.errors }
}
}
function useForm(initialValues: Record<string, string>) {
const [state, dispatch] = useReducer(formReducer, {
values: initialValues,
errors: {},
isSubmitting: false,
})
const setField = (field: string, value: string) =>
dispatch({ type: 'SET_FIELD', field, value })
const submit = async (handler: (values: Record<string, string>) => Promise<void>) => {
dispatch({ type: 'SUBMIT_START' })
try {
await handler(state.values)
dispatch({ type: 'SUBMIT_SUCCESS' })
} catch (err) {
dispatch({ type: 'SUBMIT_FAILURE', errors: { form: String(err) } })
}
}
return { ...state, setField, submit }
}
The correct way to subscribe React to any external mutable store (vanilla stores, browser APIs, third-party state).
import { useSyncExternalStore } from 'react'
function createStore<T>(initialState: T) {
let state = initialState
const listeners = new Set<() => void>()
return {
getState: () => state,
setState: (next: T | ((prev: T) => T)) => {
state = typeof next === 'function' ? (next as (prev: T) => T)(state) : next
listeners.forEach((listener) => listener())
},
subscribe: (listener: () => void) => {
listeners.add(listener)
return () => listeners.delete(listener)
},
}
}
const counterStore = createStore({ count: 0 })
function useCounter() {
const state = useSyncExternalStore(
counterStore.subscribe,
counterStore.getState,
counterStore.getState // server snapshot
)
return {
count: state.count,
increment: () => counterStore.setState((s) => ({ count: s.count + 1 })),
}
}
| Hook | Purpose | Re-renders? |
|---|---|---|
| useState | Simple state | Yes |
| useReducer | Complex state transitions | Yes |
| useRef | Mutable value, DOM refs | No |
| useMemo | Cache expensive computation | No (returns cached) |
| useCallback | Cache function reference | No (returns cached) |
| useEffect | Side effects after render | No (runs after) |
| useSyncExternalStore | Subscribe to external store | Yes (on store change) |
Rules of Hooks: (1) Only call at the top level. (2) Only call from React functions or custom hooks.