asyncredux-wait-fail-succeed
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
Best use case
asyncredux-wait-fail-succeed is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
Teams using asyncredux-wait-fail-succeed should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/asyncredux-wait-fail-succeed/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How asyncredux-wait-fail-succeed Compares
| Feature / Agent | asyncredux-wait-fail-succeed | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Show loading states and handle action failures in widgets. Covers `isWaiting(ActionType)` for spinners, `isFailed(ActionType)` for error states, `exceptionFor(ActionType)` for error messages, and `clearExceptionFor()` to reset failure states.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# AsyncRedux Wait, Fail, Succeed
AsyncRedux provides context extension methods to track async action states: waiting (in progress), failed (error), and succeeded (complete). These are essential for showing spinners, error messages, and success states in the UI.
## Four Core Methods
| Method | Returns | Purpose |
|--------|---------|---------|
| `isWaiting(ActionType)` | `bool` | True if the action is currently running |
| `isFailed(ActionType)` | `bool` | True if the action recently failed |
| `exceptionFor(ActionType)` | `UserException?` | The exception from a failed action |
| `clearExceptionFor(ActionType)` | `void` | Manually clears stored exception |
## Showing a Loading Spinner
Use `isWaiting()` to display a spinner while an action runs:
```dart
Widget build(BuildContext context) {
if (context.isWaiting(FetchDataAction)) {
return CircularProgressIndicator();
}
return Text('Data: ${context.state.data}');
}
```
The widget automatically rebuilds when the action starts and completes.
## Showing Error States
Use `isFailed()` and `exceptionFor()` to display error messages:
```dart
Widget build(BuildContext context) {
if (context.isFailed(FetchDataAction)) {
var exception = context.exceptionFor(FetchDataAction);
return Text('Error: ${exception?.message}');
}
return Text('Data: ${context.state.data}');
}
```
## Combined Pattern: Loading, Error, and Success
The typical pattern handles all three states:
```dart
Widget build(BuildContext context) {
// Loading state
if (context.isWaiting(GetItemsAction)) {
return Center(child: CircularProgressIndicator());
}
// Error state with retry
if (context.isFailed(GetItemsAction)) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Failed to load items'),
Text(context.exceptionFor(GetItemsAction)?.message ?? ''),
ElevatedButton(
onPressed: () => context.dispatch(GetItemsAction()),
child: Text('Retry'),
),
],
);
}
// Success state
return ListView.builder(
itemCount: context.state.items.length,
itemBuilder: (context, index) => ListTile(
title: Text(context.state.items[index].name),
),
);
}
```
## Automatic Error Clearing
When an action is dispatched again, any previous error for that action type is automatically cleared. This means:
- User sees error
- User taps "Retry" which dispatches the action again
- `isFailed()` becomes false immediately
- `isWaiting()` becomes true
- If action succeeds, widget shows success state
- If action fails again, `isFailed()` becomes true with the new exception
## Manual Error Clearing
Use `clearExceptionFor()` when you need to dismiss an error without retrying:
```dart
Widget build(BuildContext context) {
if (context.isFailed(SubmitFormAction)) {
return AlertDialog(
title: Text('Error'),
content: Text(context.exceptionFor(SubmitFormAction)?.message ?? ''),
actions: [
TextButton(
onPressed: () {
context.clearExceptionFor(SubmitFormAction);
},
child: Text('Dismiss'),
),
TextButton(
onPressed: () => context.dispatch(SubmitFormAction()),
child: Text('Retry'),
),
],
);
}
// ...
}
```
## How Actions Fail
Actions fail when they throw an error in `before()` or `reduce()`. Use `UserException` for user-facing errors:
```dart
class FetchDataAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final response = await api.fetchData();
if (response.statusCode == 404) {
throw UserException('Data not found.');
}
if (response.statusCode != 200) {
throw UserException('Failed to load data. Please try again.');
}
return state.copy(data: response.data);
}
}
```
## Checking Multiple Actions
You can check multiple action types for waiting or failure:
```dart
Widget build(BuildContext context) {
// Check if any of several actions are running
bool isLoading = context.isWaiting(FetchUserAction) ||
context.isWaiting(FetchSettingsAction);
if (isLoading) {
return CircularProgressIndicator();
}
// Check for any failures
if (context.isFailed(FetchUserAction)) {
return Text('Failed to load user');
}
if (context.isFailed(FetchSettingsAction)) {
return Text('Failed to load settings');
}
return MyContent();
}
```
## Pull-to-Refresh Integration
Combine with `dispatchAndWait()` for refresh indicators:
```dart
class MyListWidget extends StatelessWidget {
Future<void> _onRefresh(BuildContext context) {
return context.dispatchAndWait(RefreshItemsAction());
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () => _onRefresh(context),
child: ListView.builder(
itemCount: context.state.items.length,
itemBuilder: (context, index) => ListTile(
title: Text(context.state.items[index].name),
),
),
);
}
}
```
## Complete Example
```dart
class LoadProductsAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
final products = await api.fetchProducts();
if (products.isEmpty) {
throw UserException('No products available.');
}
return state.copy(products: products);
}
}
class ProductsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Products')),
body: _buildBody(context),
floatingActionButton: FloatingActionButton(
onPressed: () => context.dispatch(LoadProductsAction()),
child: Icon(Icons.refresh),
),
);
}
Widget _buildBody(BuildContext context) {
if (context.isWaiting(LoadProductsAction)) {
return Center(child: CircularProgressIndicator());
}
if (context.isFailed(LoadProductsAction)) {
final error = context.exceptionFor(LoadProductsAction);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, size: 64, color: Colors.red),
SizedBox(height: 16),
Text(error?.message ?? 'An error occurred'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.dispatch(LoadProductsAction()),
child: Text('Try Again'),
),
],
),
);
}
final products = context.state.products;
if (products.isEmpty) {
return Center(child: Text('No products yet. Tap refresh to load.'));
}
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) => ListTile(
title: Text(products[index].name),
subtitle: Text('\$${products[index].price}'),
),
);
}
}
```
## References
URLs from the documentation:
- https://asyncredux.com/flutter/basics/wait-fail-succeed
- https://asyncredux.com/flutter/miscellaneous/advanced-waiting
- https://asyncredux.com/flutter/advanced-actions/action-status
- https://asyncredux.com/flutter/basics/failed-actions
- https://asyncredux.com/flutter/advanced-actions/errors-thrown-by-actions
- https://asyncredux.com/flutter/basics/using-the-store-state
- https://asyncredux.com/flutter/basics/dispatching-actions
- https://asyncredux.com/flutter/basics/async-actions
- https://asyncredux.com/flutter/miscellaneous/refresh-indicatorsRelated Skills
asyncredux-wait-condition
Use `waitCondition()` inside actions to pause execution until state meets criteria. Covers waiting for price thresholds, coordinating between actions, and implementing conditional workflows.
asyncredux-user-exceptions
Handle user-facing errors with UserException. Covers throwing UserException from actions, setting up UserExceptionDialog, customizing error dialogs with `onShowUserExceptionDialog`, and using UserExceptionAction for non-interrupting error display.
asyncredux-sync-actions
Creates AsyncRedux (Flutter) synchronous actions that update state immediately by implementing reduce() to return a new state.
asyncredux-streams-timers
Manage Streams and Timers with AsyncRedux. Covers creating actions to start/stop streams, storing stream subscriptions in store props, dispatching actions from stream callbacks, and proper cleanup with disposeProps().
asyncredux-state-access
Access store state in widgets using `context.state`, `context.select()`, and `context.read()`. Covers when to use each method, setting up BuildContext extensions, and optimizing widget rebuilds with selective state access.
asyncredux-setup
Initialize, setup and configure AsyncRedux in a Flutter app. Use it whenever starting a new AsyncRedux project, or when the user requests.
asyncredux-selectors
Create and cache selectors for efficient state access. Covers writing selector functions, caching with `cache1` and `cache2`, the reselect pattern, and avoiding repeated computations in widgets.
asyncredux-persistence
Implement local state persistence using Persistor. Covers creating a custom Persistor class, implementing `readState()`, `persistDifference()`, `deleteState()`, using LocalPersist helper, throttling saves, and pausing/resuming persistence with app lifecycle.
asyncredux-optimistic-update-mixin
Add the OptimisticUpdate mixin for instant UI feedback before server confirmation. Covers immediate state changes, automatic rollback on failure, and optionally notifying users of rollback.
asyncredux-navigation
Handle navigation through actions using NavigateAction. Covers setting up the navigator key, dispatching NavigateAction for push/pop/replace, and testing navigation in isolation.
asyncredux-events
Use the Event class to interact with Flutter's stateful widgets (TextField, ListView, etc.). Covers creating Event objects in state, consuming events with `context.event()`, scrolling lists, changing text fields, and the event lifecycle.
asyncredux-dependency-injection
Inject dependencies into actions using the environment pattern. Covers creating an Environment class, passing it to the Store, accessing `env` from actions, and using dependency injection for testability.