Claude-skill-registry asyncredux-flutter-hooks
Integrate AsyncRedux with the flutter_hooks package. Covers adding flutter_hooks_async_redux, using the useSelector hook, and combining hooks with AsyncRedux state management.
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/asyncredux-flutter-hooks" ~/.claude/skills/majiayu000-claude-skill-registry-asyncredux-flutter-hooks && rm -rf "$T"
skills/data/asyncredux-flutter-hooks/SKILL.mdOverview
The
flutter_hooks_async_redux package provides a hooks-based API for accessing AsyncRedux state. If you prefer functional components with hooks over the widget-based StoreConnector pattern, this package lets you use hooks like useSelector and useDispatch to interact with the Redux store.
Installation
Add these dependencies to your
pubspec.yaml:
dependencies: flutter_hooks: ^0.21.2 async_redux: ^24.2.2 flutter_hooks_async_redux: ^3.1.0
Then run
flutter pub get.
Core Hooks
useSelector
Selects a part of the state and subscribes to updates. The widget rebuilds when the selected value changes:
String username = useSelector<AppState, String>((state) => state.username);
The
distinct parameter (default true) controls whether the widget rebuilds only when the selected value changes.
Creating a Custom useAppState Hook
For convenience, define a custom hook that's pre-typed for your state:
T useAppState<T>(T Function(AppState state) converter, {bool distinct = true}) => useSelector<AppState, T>(converter, distinct: distinct);
This simplifies state access throughout your app:
// Instead of: String username = useSelector<AppState, String>((state) => state.username); // Use: String username = useAppState((state) => state.username);
useDispatch
Dispatches actions that may change the store state. Works with both sync and async actions:
class MyWidget extends HookWidget { @override Widget build(BuildContext context) { var dispatch = useDispatch(); return ElevatedButton( onPressed: () => dispatch(IncrementAction()), child: Text('Increment'), ); } }
useDispatchAndWait
Dispatches an action and returns a
Future<ActionStatus> that resolves when the action completes:
class MyWidget extends HookWidget { @override Widget build(BuildContext context) { var dispatchAndWait = useDispatchAndWait(); var dispatch = useDispatch(); Future<void> handleSubmit() async { // Wait for first action to complete await dispatchAndWait(DoThisFirstAction()); // Then dispatch the second dispatch(DoThisSecondAction()); } return ElevatedButton( onPressed: handleSubmit, child: Text('Submit'), ); } }
You can also check the action status:
var status = await dispatchAndWait(MyAction()); if (status.isCompletedOk) { // Action succeeded }
useDispatchSync
Enforces synchronous action dispatch. Throws
StoreException if you attempt to dispatch an async action:
var dispatchSync = useDispatchSync(); dispatchSync(MySyncAction()); // OK dispatchSync(MyAsyncAction()); // Throws StoreException
Waiting and Error Hooks
useIsWaiting
Checks if an async action is currently being processed:
class MyWidget extends HookWidget { @override Widget build(BuildContext context) { var dispatch = useDispatch(); var isLoading = useIsWaiting(LoadDataAction); return Column( children: [ if (isLoading) CircularProgressIndicator(), ElevatedButton( onPressed: () => dispatch(LoadDataAction()), child: Text('Load'), ), ], ); } }
You can check by action type, action instance, or multiple types:
// By action type var isWaiting = useIsWaiting(MyAction); // By action instance var action = MyAction(); dispatch(action); var isWaiting = useIsWaiting(action); // Multiple types - true if ANY are in progress var isWaiting = useIsWaiting([BuyAction, SellAction]);
useIsFailed
Checks if an action has failed:
var isFailed = useIsFailed(MyAction); if (isFailed) { return Text('Something went wrong'); }
useExceptionFor
Retrieves the
UserException from a failed action:
var exception = useExceptionFor(MyAction); if (exception != null) { return Text(exception.reason ?? 'Unknown error'); }
useClearExceptionFor
Gets a function to clear the exception state for an action:
var clearExceptionFor = useClearExceptionFor(); // Clear exception when user dismisses error ElevatedButton( onPressed: () => clearExceptionFor(MyAction), child: Text('Dismiss'), )
Complete Example
Here's a full example combining multiple hooks:
class UserProfileWidget extends HookWidget { @override Widget build(BuildContext context) { // Select state var username = useAppState((state) => state.user.name); var email = useAppState((state) => state.user.email); // Dispatch hooks var dispatch = useDispatch(); var dispatchAndWait = useDispatchAndWait(); // Loading and error state var isLoading = useIsWaiting(UpdateProfileAction); var isFailed = useIsFailed(UpdateProfileAction); var exception = useExceptionFor(UpdateProfileAction); var clearException = useClearExceptionFor(); Future<void> handleUpdate() async { var status = await dispatchAndWait(UpdateProfileAction()); if (status.isCompletedOk) { // Show success message } } return Column( children: [ Text('Username: $username'), Text('Email: $email'), if (isLoading) CircularProgressIndicator(), if (isFailed && exception != null) Row( children: [ Text(exception.reason ?? 'Update failed'), IconButton( icon: Icon(Icons.close), onPressed: () => clearException(UpdateProfileAction), ), ], ), ElevatedButton( onPressed: isLoading ? null : handleUpdate, child: Text('Update Profile'), ), ], ); } }
Hook Parameters Reference
| Hook | Accepts | Returns |
|---|---|---|
| Converter function | Selected value of type T |
| None | Dispatch function |
| None | Function returning |
| None | Sync dispatch function |
| Action type, instance, or list of types | |
| Action type, instance, or list of types | |
| Action type, instance, or list of types | |
| None | Clear function |
Hooks vs StoreConnector
Choose hooks when:
- You prefer functional widget patterns
- You're already using
in your projectflutter_hooks - You want concise state access without view-model boilerplate
Choose
StoreConnector when:
- You want explicit separation between UI and state logic
- You need the structured view-model pattern for testing
- You're not using hooks elsewhere in your project
Both approaches work well with AsyncRedux - pick the one that fits your team's preferences.
References
URLs from the documentation: