Skip to main content

react

Use when implementing advanced React patterns — designing custom hooks, using Suspense or error boundaries, applying TypeScript generics to components and hooks, or building animated UI with Framer Motion, View Transitions, or scroll-driven animations. For Next.js App Router, Server Components, or Server Actions, use nextjs.

Source facts

Repository
kid-sid/claude-spellbook
Last source activity
May 27, 2026 at 17:29
Detected SKILL.md language
English
Stars
190
Forks
16

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
react
description
Use when implementing advanced React patterns — designing custom hooks, using Suspense or error boundaries, applying TypeScript generics to components and hooks, or building animated UI with Framer Motion, View Transitions, or scroll-driven animations. For Next.js App Router, Server Components, or Server Actions, use nextjs.
# React — Advanced Patterns > For state management (Zustand/Redux), TanStack Query, React Hook Form, React Router, and component testing, see the `frontend` skill. ## When to Activate - Designing or debugging custom hooks - Choosing between Context, Zustand, or prop drilling - Using Suspense, error boundaries, or concurrent features (`useTransition`, `useDeferredValue`) - Building compound components or headless/renderless components - Working in Next.js App Router (Server Components, Server Actions, streaming) - TypeScript generics, event types, or `forwardRef` patterns - `useRef`, `useImperativeHandle`, portals, or advanced DOM integration --- ## Hooks Deep Dive ### useReducer — when useState gets complex ```tsx type State = { count: number; error: string | null; loading: boolean }; type Action = | { type: "increment" } | { type: "set_error"; payload: string } | { type: "reset" }; function reducer(state: State, action: Action): State { switch (action.type) { case "increment": return { ...state, count: state.count + 1 }; case "set_error": return { ...state, error: action.payload, loading: false }; case "reset": return { count: 0, error: null, loading: false }; } } const [state, dispatch] = useReducer(reducer, { count: 0, error: null, loading: false }); dispatch({ type: "increment" }); ``` Use `useReducer` over `useState` when: multiple related state fields, next state depends on previous, or actions have semantic names that make logic readable. ### useRef — three distinct uses ```tsx // 1. DOM access const inputRef = useRef<HTMLInputElement>(null); useEffect(() => { inputRef.current?.focus(); }, []); // 2. Mutable value that doesn't trigger re-render (e.g. interval ID, previous value) const timerRef = useRef<NodeJS.Timeout | null>(null); const prevValueRef = useRef(value); useEffect(() => { prevValueRef.current = value; }); // 3. Stable callback reference (avoids stale closure in event listeners) const callbackRef = useRef(onSave); useEffect(() => { callbackRef.current = onSave; }); useEffect(() => { const handler = () => callbackRef.current(); window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, []); // dep array stays empty — no stale closure ``` ### useContext — subscribe only to what you need ```tsx // Split large contexts to prevent unnecessary re-renders const UserDataContext = createContext<UserData | null>(null); const UserActionsContext = createContext<UserActions | null>(null); // Custom hook enforces non-null and co-locates error message function useUserData() { const ctx = useContext(UserDataContext); if (!ctx) throw new Error("useUserData must be used inside UserProvider"); return ctx; } // Provider combines both function UserProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState<UserData | null>(null); const actions = useMemo(() => ({ login: ..., logout: ... }), []); // stable ref return ( <UserDataContext.Provider value={user}> <UserActionsContext.Provider value={actions}> {children} </UserActionsContext.Provider> </UserDataContext.Provider> ); } ``` **Context is NOT a performance-free global store.** Every consumer re-renders when the value changes. Use Zustand for frequently-changing shared state; Context for stable config (theme, locale, auth user). ### Concurrent Features ```tsx // useTransition — mark state update as non-urgent (keeps UI responsive) const [isPending, startTransition] = useTransition(); function handleSearch(query: string) { setInputValue(query); // urgent — update input immediately startTransition(() => { setFilteredResults(filter(query)); // non-urgent — can be interrupted }); } // useDeferredValue — defer a derived value (use when you don't own the state setter) const deferredQuery = useDeferredValue(searchQuery); const results = useMemo(() => filter(deferredQuery), [deferredQuery]); const isStale = searchQuery !== deferredQuery; // show loading indicator // useId — stable unique IDs across server/client (avoids hydration mismatch) function FormField({ label }: { label: string }) { const id = useId(); return ( <> <label htmlFor={id}>{label}</label> <input id={id} /> </> ); } ``` --- ## Custom Hooks Extract logic into a hook when: the same `useEffect` + `useState` combo appears twice, or a component mixes UI with data-fetching concerns. ```tsx // useLocalStorage — syncs state with localStorage function useLocalStorage<T>(key: string, initialValue: T) { const [value, setValue] = useState<T>(() => { try { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; } catch { return initialValue; } }); const set = useCallback((newValue: T | ((prev: T) => T)) => { setValue(prev => { const next = newValue instanceof Function ? newValue(prev) : newValue; localStorage.setItem(key, JSON.stringify(next)); return next; }); }, [key]); return [value, set] as const; } // useDebounce — delay reacting to fast-changing input function useDebounce<T>(value: T, delay: number): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } // useEventListener — typed, auto-cleaned function useEventListener<K extends keyof WindowEventMap>( event: K, handler: (e: WindowEventMap[K]) => void, element: EventTarget = window, ) { const handlerRef = useRef(handler); useEffect(() => { handlerRef.current = handler; }); useEffect(() => { const fn = (e: Event) => handlerRef.current(e as WindowEventMap[K]); element.addEventListener(event, fn); return () => element.removeEventListener(event, fn); }, [event, element]); } ``` --- ## Compound Components Let parent manage state; children access it via Context. No prop drilling, flexible composition. ```tsx const TabsContext = createContext<{ active: string; setActive: (id: string) => void } | null>(null); const useTabs = () => { const ctx = useContext(TabsContext); if (!ctx) throw new Error("Must be used inside <Tabs>"); return ctx; }; function Tabs({ defaultTab, children }: { defaultTab: string; children: ReactNode }) { const [active, setActive] = useState(defaultTab); return ( <TabsContext.Provider value={{ active, setActive }}> <div>{children}</div> </TabsContext.Provider> ); } function Tab({ id, children }: { id: string; children: ReactNode }) { const { active, setActive } = useTabs(); return ( <button role="tab" aria-selected={active === id} onClick={() => setActive(id)} > {children} </button> ); } function TabPanel({ id, children }: { id: string; children: ReactNode }) { const { active } = useTabs(); return active === id ? <div role="tabpanel">{children}</div> : null; } Tabs.Tab = Tab; Tabs.Panel = TabPanel; // Usage <Tabs defaultTab="overview"> <Tabs.Tab id="overview">Overview</Tabs.Tab> <Tabs.Tab id="settings">Settings</Tabs.Tab> <Tabs.Panel id="overview"><OverviewContent /></Tabs.Panel> <Tabs.Panel id="settings"><SettingsContent /></Tabs.Panel> </Tabs> ``` --- ## Error Boundaries React errors during render are caught by the nearest error boundary. Must be a class component (or use `react-error-boundary` library). ```tsx import { ErrorBoundary } from "react-error-boundary"; function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { return ( <div role="alert"> <p>Something went wrong:</p> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> ); } // Wrap any subtree that might throw during render <ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => queryClient.resetQueries()} onError={(error, info) => logger.error(error, info)} > <UserDashboard /> </ErrorBoundary> ``` Error boundaries do **not** catch: async errors (use `try/catch`), event handler errors, or server-side errors. --- ## Suspense ```tsx // Suspense shows fallback while children are loading (lazy imports or data) <Suspense fallback={<Spinner />}> <LazyComponent /> </Suspense> // Nested Suspense — granular loading states <Suspense fallback={<PageSkeleton />}> <PageHeader /> <Suspense fallback={<TableSkeleton />}> <DataTable /> {/* streams in independently */} </Suspense> </Suspense> // Combine with ErrorBoundary <ErrorBoundary FallbackComponent={ErrorFallback}> <Suspense fallback={<Spinner />}> <AsyncComponent /> </Suspense> </ErrorBoundary> ``` --- ## forwardRef and useImperativeHandle ```tsx // forwardRef — pass ref through to a DOM element const Input = forwardRef<HTMLInputElement, InputProps>(function Input(props, ref) { return <input ref={ref} {...props} />; }); // useImperativeHandle — expose a custom API instead of the raw DOM node interface DialogHandle { open: () => void; close: () => void } const Dialog = forwardRef<DialogHandle, DialogProps>(function Dialog(props, ref) { const [open, setOpen] = useState(false); useImperativeHandle(ref, () => ({ open: () => setOpen(true), close: () => setOpen(false), })); return open ? <div>{props.children}</div> : null; }); // Usage const dialogRef = useRef<DialogHandle>(null); dialogRef.current?.open(); ``` --- ## Portals Render outside the component tree (modals, tooltips, toasts) without CSS stacking-context issues. ```tsx import { createPortal } from "react-dom"; function Modal({ children, onClose }: ModalProps) { return createPortal( <div className="modal-overlay" onClick={onClose}> <div className="modal-content" onClick={e => e.stopPropagation()}> {children} </div> </div>, document.body, // renders at the bottom of <body>, outside the app root ); } ``` --- ## TypeScript Patterns ```tsx // Generic component function List<T extends { id: string }>({ items, renderItem, }: { items: T[]; renderItem: (item: T) => ReactNode; }) { return <ul>{items.map(item => <li key={item.id}>{renderItem(item)}</li>)}</ul>; } // Event types const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); ... }; const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { ... }; // Polymorphic component (renders as any element) type ButtonProps<T extends ElementType = "button"> = { as?: T; children: ReactNode; } & ComponentPropsWithoutRef<T>; function Button<T extends ElementType = "button">({ as, children, ...props }: ButtonProps<T>) { const Component = as ?? "button"; return <Component {...props}>{children}</Component>; }
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub