| name | hooks-validator |
| description | Validates React code for Rules of Hooks violations. Catches issues like hooks called after conditional returns that cause production crashes (React error |
React 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