Claude-skill-registry hooks-validator
Validates React code for Rules of Hooks violations. Catches issues like hooks called after conditional returns that cause production crashes (React error #310). Use when creating or modifying screens, components, or custom hooks.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/hooks-validator" ~/.claude/skills/majiayu000-claude-skill-registry-hooks-validator && rm -rf "$T"
skills/data/hooks-validator/SKILL.mdReact Hooks Validator
This skill analyzes React code to detect Rules of Hooks violations that cause production crashes.
Background
The Ishkul codebase experienced a production crash (React error #310) in LessonScreen where hooks were called after conditional returns. This skill prevents similar issues.
Rules of Hooks
React hooks must:
- Always be called at the top level - Never inside loops, conditions, or nested functions
- Always be called in the same order - On every render
- Only be called from React functions - Components or custom hooks
Validation Checklist
When analyzing React code, check for these violations:
Critical Violations (Will Crash)
-
Hooks after conditional returns
// BAD - Hook called after conditional return export const Component = () => { if (loading) return <Spinner />; const [state, setState] = useState(null); // Will crash! return <Content />; }; // GOOD - All hooks at top export const Component = () => { const [state, setState] = useState(null); if (loading) return <Spinner />; return <Content />; }; -
Hooks inside conditionals
// BAD if (user) { const [data, setData] = useState(null); } // GOOD const [data, setData] = useState(user ? null : undefined); -
Hooks inside loops
// BAD items.forEach(() => { const [itemState] = useState({}); }); // GOOD - Single state for all items const [itemStates, setItemStates] = useState({}); -
Hooks inside callbacks
// BAD const handleClick = () => { const [state] = useState(null); }; // GOOD - Hook outside callback const [state, setState] = useState(null); const handleClick = () => { setState(newValue); };
Ishkul-Specific Patterns
From the codebase patterns, ensure:
-
Zustand store hooks at top
export const Screen = () => { // All Zustand hooks first const { lesson, isLoading, error } = useLessonStore(); const { courses } = useCoursesStore(); // Then other hooks const { colors } = useTheme(); const navigation = useNavigation(); // Extract state used in multiple render paths BEFORE conditionals const completedBlocks = lesson?.completedBlocks ?? []; // Callbacks with useCallback BEFORE conditionals const handleSubmit = useCallback(() => { // ... }, [dependencies]); // NOW safe to have conditional returns if (isLoading) return <LoadingState />; if (error) return <ErrorState error={error} />; if (!lesson) return <NotFoundState />; return <SuccessState lesson={lesson} />; }; -
Custom hooks must follow same rules
// Custom hook in frontend/src/hooks/ export function useLesson(options: UseLessonOptions) { // All hooks at top const prevLessonIdRef = useRef<string | null>(null); const { currentLesson, fetchLesson } = useLessonStore(); const { getNextLesson } = useCoursesStore(); // Effects after hooks useEffect(() => { // ... }, [deps]); // Never return early before all hooks are called return { lesson: currentLesson, // ... }; }
How to Analyze
When reviewing a file:
- List all hook calls in order of appearance
- Check for early returns that could cause hooks to be skipped
- Verify hooks are not inside:
- if/else blocks
- for/while loops
- forEach/map callbacks
- Event handlers
- try/catch blocks (hooks should be outside)
- Check custom hooks call other hooks at the top level
Output Format
When violations are found, report:
## Hooks Violations Found ### File: `path/to/file.tsx` **Violation #1** (Critical) - **Type**: Hook after conditional return - **Line**: 45 - **Hook**: `useState` - **Issue**: `useState` is called after `if (loading) return <Spinner />` - **Fix**: Move `useState` before the conditional return ```typescript // Current (line 42-48) if (loading) return <Spinner />; const [data, setData] = useState(null); // Fixed const [data, setData] = useState(null); if (loading) return <Spinner />;
## Integration with Testing After fixing hooks violations, ensure state transition tests exist: ```typescript describe('State Transitions (Rules of Hooks)', () => { it('should handle transition from loading to success', async () => { const { rerender } = render(<Component />); // Initially loading mockState.loading = true; rerender(<Component />); // Then success mockState.loading = false; mockState.data = { ... }; rerender(<Component />); // Should not crash! }); });
When to Use
- When creating new screens in
frontend/src/screens/ - When creating new components with state in
frontend/src/components/ - When modifying existing components with hooks
- When creating custom hooks in
frontend/src/hooks/ - During code review before merging PRs