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.

16 stars

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

$curl -o ~/.claude/skills/asyncredux-wait-fail-succeed/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/design/asyncredux-wait-fail-succeed/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/asyncredux-wait-fail-succeed/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How asyncredux-wait-fail-succeed Compares

Feature / Agentasyncredux-wait-fail-succeedStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/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-indicators

Related Skills

asyncredux-wait-condition

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

Creates AsyncRedux (Flutter) synchronous actions that update state immediately by implementing reduce() to return a new state.

asyncredux-streams-timers

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

Initialize, setup and configure AsyncRedux in a Flutter app. Use it whenever starting a new AsyncRedux project, or when the user requests.

asyncredux-selectors

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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

16
from diegosouzapw/awesome-omni-skill

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.