Guides writing React hooks with conventions for object returns, SSR safety, state design, effect patterns, cleanup, TypeScript, and performance.
npx claudepluginhub toss/react-simplikit --plugin react-design-philosophyThis skill uses the workspace's default tool permissions.
Design principles for writing React hooks. Each section covers What + Why.
Guides design of React APIs and abstractions following principles like declarative interfaces, lifecycle safety, minimal surfaces, type safety, and zero dependencies. For higher-level abstraction decisions.
Guides React Hooks usage including useState, useEffect, useContext, useReducer, useMemo, useCallback, custom hooks, and patterns for state, effects, and performance in functional components.
Generates React hooks with step-by-step guidance, best practices, and production-ready code. Activates for frontend development tasks on 'react hook creator' mentions, covering React, Vue, CSS, accessibility, and performance.
Share bugs, ideas, or general feedback.
Design principles for writing React hooks. Each section covers What + Why.
Treat C1 and C7 as project conventions rather than universal React rules. React itself allows tuple returns and positional arguments, but this philosophy prefers objects for extensibility and self-documenting APIs.
Return values (C1): Always return objects. Even single values use { value }.
Why: Named fields, order-independent, extensible without breaking changes.
function useDebounce<T>({ value, delay }: { value: T; delay: number }): {
value: T;
};
function useToggle({ initial }: { initial?: boolean }): {
value: boolean;
toggle: () => void;
};
Parameters (C7): Object props, not positional. Order-independent + self-documenting.
Named exports (C5): No default exports.
Fixed initial + useEffect sync (C2). Never call browser APIs in useState initializer.
const [width, setWidth] = useState(0);
useEffect(function syncWidth() {
setWidth(window.innerWidth);
}, []);
For client-only apps: conditional initializer useState(() => { if (typeof window === 'undefined') return 0; ... }) is acceptable.
Derive, don't sync (U1): Compute from existing state during render. No useEffect for derived values.
// Compute during render
const fullName = firstName + ' ' + lastName;
useRef for non-rendered values (U3): Interval IDs, flags, previous values.
Discriminated unions (U5): Replace boolean combos with type Status = 'idle' | 'loading' | 'done'.
Don't mirror props (U2): Use directly, or name initialX.
IDs not objects (U6), group related state (U7).
Guard clauses (C8): Prefer early returns over nested conditionals so the happy path stays flat and the failure path is obvious.
Effects for sync only (U8). External systems (network, DOM, browser APIs). Not for event handling or data transforms.
No effect chains (U9). Consolidate cascading setState into event handlers or reducers.
Key reset (U10): key={id} to remount cleanly, not useEffect to clear state.
Deps inside effect (U11): Objects/functions used only in effect — define inside.
Parent notify in handler (U13): Call parent callback in same event handler, not effect.
useSyncExternalStore (U12): For browser API or third-party store subscriptions, prefer useSyncExternalStore over useState + useEffect. Prevents tearing in concurrent rendering and supports SSR server snapshots.
Named useEffect functions (C14): Optional but recommended for stack traces and debugging clarity.
Every side effect needs cleanup. Three patterns:
// Event listeners
return () => window.removeEventListener('resize', handler);
// Async (AbortController)
const controller = new AbortController();
return () => controller.abort();
// Timers
return () => clearInterval(id);
Async effects need ignore flags or AbortController to prevent race conditions (U14).
Apply only to >30 events/sec (scroll, resize, keyboard):
useMemo (U15): Only for measured >= 1ms computations. useCallback (U16): Only when passing to memo()-wrapped children.
<T> (C4): No any. Justified eslint-disable with comment is acceptable.as const for tuple returns (if ever needed).== null for nullish, !== undefined for distinction./**
* @description [One-line summary]
* @param {{ value: T; delay: number }} params - Hook parameters
* @returns {{ value: T }} Debounced value
* @example
* const { value } = useDebounce({ value: query, delay: 300 });
*/
See patterns.md for 3 complete hook implementations.
Use react-design-principles when the question is about higher-level React API or abstraction design rather than a specific hook implementation.