xiaohongshu
xiaohongshu
asyncredux-before-after
Implement action lifecycle methods `before()` and `after()`. Covers running precondition checks, showing/hiding modal barriers, cleanup logic in `after()`, and understanding that `after()` always runs (like a finally block).
Full skill instructions
Every ReduxAction has three lifecycle methods that execute in order:
before() - Runs first, before the reducerreduce() - The main reducer (required)after() - Runs last, always executesOnly reduce() is required. The before() and after() methods are optional hooks for managing side effects.
The before() method executes before the reducer runs. It can be synchronous or asynchronous.
class MyAction extends ReduxAction<AppState> {
@override
void before() {
// Runs synchronously before reduce()
print('Action starting');
}
@override
AppState? reduce() {
return state.copy(counter: state.counter + 1);
}
}
class MyAction extends ReduxAction<AppState> {
@override
Future<void> before() async {
// Runs asynchronously before reduce()
await validatePermissions();
}
@override
Future<AppState?> reduce() async {
final data = await fetchData();
return state.copy(data: data);
}
}
If before() throws an error, reduce() will NOT run. This makes it ideal for validation:
class FetchUserData extends ReduxAction<AppState> {
@override
Future<void> before() async {
if (!await hasInternetConnection()) {
throw UserException('No internet connection');
}
}
@override
Future<AppState?> reduce() async {
// Only runs if before() completed without error
final user = await api.fetchUser();
return state.copy(user: user);
}
}
The after() method executes after the reducer completes. Its key property: it always runs, even if before() or reduce() throws an error. This makes it similar to a finally block.
class MyAction extends ReduxAction<AppState> {
@override
AppState? reduce() {
return state.copy(counter: state.counter + 1);
}
@override
void after() {
// Always runs, regardless of success or failure
print('Action completed');
}
}
Because after() always runs, it's perfect for cleanup operations:
class SaveDocument extends ReduxAction<AppState> {
@override
Future<void> before() async {
dispatch(ShowSavingIndicatorAction(true));
}
@override
Future<AppState?> reduce() async {
await api.saveDocument(state.document);
return state.copy(lastSaved: DateTime.now());
}
@override
void after() {
// Hides indicator even if save fails
dispatch(ShowSavingIndicatorAction(false));
}
}
The after() method should never throw errors. Any exception thrown from after() will appear asynchronously in the console and cannot be caught normally:
// WRONG - Don't throw in after()
@override
void after() {
if (someCondition) {
throw Exception('This will cause problems');
}
}
// CORRECT - Handle errors gracefully
@override
void after() {
try {
cleanup();
} catch (e) {
// Log but don't throw
logger.error('Cleanup failed: $e');
}
}
A common pattern is showing a modal barrier (blocking overlay) during async operations:
class MyAction extends ReduxAction<AppState> {
@override
Future<AppState?> reduce() async {
String description = await read(Uri.http("numbersapi.com", "${state.counter}"));
return state.copy(description: description);
}
@override
void before() => dispatch(BarrierAction(true));
@override
void after() => dispatch(BarrierAction(false));
}
The BarrierAction would update state to show/hide a loading overlay:
class BarrierAction extends ReduxAction<AppState> {
final bool show;
BarrierAction(this.show);
@override
AppState reduce() => state.copy(showBarrier: show);
}
For patterns you use repeatedly, create a mixin:
mixin Barrier on ReduxAction<AppState> {
@override
void before() {
super.before();
dispatch(BarrierAction(true));
}
@override
void after() {
dispatch(BarrierAction(false));
super.after();
}
}
Then apply it to any action:
class FetchData extends ReduxAction<AppState> with Barrier {
@override
Future<AppState?> reduce() async {
// Barrier shown automatically before this runs
final data = await api.fetchData();
return state.copy(data: data);
// Barrier hidden automatically after (even on error)
}
}
You can combine multiple mixins:
class ImportantAction extends ReduxAction<AppState> with Barrier, NonReentrant {
@override
Future<AppState?> reduce() async {
// Has both modal barrier AND prevents duplicate dispatches
return state;
}
}
Understanding how errors interact with the lifecycle:
class MyAction extends ReduxAction<AppState> {
@override
Future<void> before() async {
// If this throws, reduce() is skipped, after() still runs
}
@override
Future<AppState?> reduce() async {
// If this throws, state is not changed, after() still runs
}
@override
void after() {
// ALWAYS runs regardless of errors above
}
}
Use ActionStatus to determine which methods finished:
var status = await dispatchAndWait(MyAction());
if (status.hasFinishedMethodBefore) {
print('before() completed');
}
if (status.hasFinishedMethodReduce) {
print('reduce() completed');
}
if (status.hasFinishedMethodAfter) {
print('after() completed');
}
if (status.isCompletedOk) {
print('Both before() and reduce() completed without errors');
}
if (status.isCompletedFailed) {
print('Error: ${status.originalError}');
}
If abortDispatch() returns true, none of the lifecycle methods run:
class MyAction extends ReduxAction<AppState> {
@override
bool abortDispatch() => state.user == null;
@override
void before() {
// Skipped if abortDispatch() returns true
}
@override
AppState? reduce() {
// Skipped if abortDispatch() returns true
}
@override
void after() {
// Skipped if abortDispatch() returns true
}
}
class SubmitForm extends ReduxAction<AppState> {
final String formData;
SubmitForm(this.formData);
@override
Future<void> before() async {
// Validate preconditions
if (state.user == null) {
throw UserException('Please log in first');
}
if (!await checkInternetConnection()) {
throw UserException('No internet connection');
}
// Show loading state
dispatch(SetSubmittingAction(true));
}
@override
Future<AppState?> reduce() async {
final result = await api.submitForm(formData);
return state.copy(
lastSubmission: result,
submissionCount: state.submissionCount + 1,
);
}
@override
void after() {
// Always hide loading state, even on error
dispatch(SetSubmittingAction(false));
// Log completion
analytics.log('form_submitted');
}
}
Several AsyncRedux mixins use these methods internally:
| Mixin | Uses before() | Uses after() | Purpose |
|---|---|---|---|
CheckInternet | Yes | No | Verifies connectivity, shows dialog if offline |
AbortWhenNoInternet | Yes | No | Silently aborts if offline |
Throttle | No | Yes | Limits execution frequency |
NonReentrant | Yes | Yes | Prevents duplicate dispatches |
Retry | No | Yes | Retries on failure |
Debounce | No | No | Waits for input pause (uses wrapReduce) |
When using these mixins, be aware that they may already override before() or after(). Call super.before() and super.after() if you need to combine behaviors.
URLs from the documentation:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience