--- description: "Hooks + Riverpod conventions for {{PROJECT_NAME}}" alwaysApply: true --- # Hooks + Riverpod Standards — {{PROJECT_NAME}} ## Widget base classes - `HookConsumerWidget` — when you need BOTH hooks and Riverpod providers - `HookWidget` — when you need ONLY hooks (no Riverpod) - `ConsumerWidget` — when you need ONLY Riverpod (no hooks) ## Hook rules ```dart class ProductSearchWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { // Hooks at the TOP of build() — never inside conditions or loops final searchQuery = useState(''); final searchCtrl = useTextEditingController(); final focusNode = useFocusNode(); final debounced = useDebounced(searchQuery.value, const Duration(milliseconds: 300)); // Riverpod below hooks final results = ref.watch(searchResultsProvider(debounced)); return // ... } } ``` ## What goes in hooks vs providers | Concern | Tool | |---------|------| | Local UI state (text controller, animation, focus) | `useState`, `useAnimationController` | | Server/async state | Riverpod `AsyncNotifier` | | Cross-widget/feature state | Riverpod providers | | Lifecycle side effects | `useEffect` | ## Rules - **NEVER** call hooks inside `if`, `for`, or callbacks - `useEffect` cleanup MUST return a dispose function - Custom hooks: prefix with `use`, live in `lib/core/hooks/` - Do not use `flutter_riverpod` `ref.watch` inside `useEffect` — use `useRef` + `ref.listen`