Claude-skill-registry 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.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
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-state-access" ~/.claude/skills/majiayu000-claude-skill-registry-asyncredux-state-access && rm -rf "$T"
manifest: skills/data/asyncredux-state-access/SKILL.md
source content

BuildContext Extension Setup

To access your application state in widgets, first define a

BuildContext
extension. Add this to your project (typically in a shared file that all widgets can import):

extension BuildContextExtension on BuildContext {
  AppState get state => getState<AppState>();
  AppState read() => getRead<AppState>();
  R select<R>(R Function(AppState state) selector) => getSelect<AppState, R>(selector);
  R? event<R>(Evt<R> Function(AppState state) selector) => getEvent<AppState, R>(selector);
}

Replace

AppState
with your actual state class name.

The Three State Access Methods

context.state

Grants access to the entire state object. All widgets that use

context.state
will automatically rebuild whenever the store state changes (any part of it).

Widget build(BuildContext context) {
  return Text('Counter: ${context.state.counter}');
}

context.select()

Retrieves only specific state portions. This is more efficient as it only rebuilds the widget when the selected part of the state changes.

Widget build(BuildContext context) {
  var counter = context.select((state) => state.counter);
  return Text('Counter: $counter');
}

context.read()

Retrieves state without triggering rebuilds. Use this in event handlers,

initState
, or anywhere you need to read state once without subscribing to changes.

void _onButtonPressed() {
  var currentCount = context.read().counter;
  print('Current count is $currentCount');
}

When to Use Each Method

MethodUse InTriggers Rebuilds?Best For
context.state
build
method
Yes, on any state changeSimple widgets or when you need many state properties
context.select()
build
method
Only when selected part changesPerformance-sensitive widgets
context.read()
initState
, event handlers, callbacks
NoOne-time reads, button handlers

Accessing Multiple State Properties

When you need several pieces of state, you have two options:

Option 1: Multiple select calls

Widget build(BuildContext context) {
  var name = context.select((state) => state.user.name);
  var email = context.select((state) => state.user.email);
  var itemCount = context.select((state) => state.items.length);
  // Widget rebuilds only if name, email, or itemCount changes
  return Text('$name ($email) - $itemCount items');
}

Option 2: Dart records for combined selection

Widget build(BuildContext context) {
  var (name, email) = context.select((state) => (state.user.name, state.user.email));
  return Text('$name ($email)');
}

Additional Context Methods for Action States

Beyond state access, the context extension provides methods for tracking async action progress:

Widget build(BuildContext context) {
  // Check if an action is currently running
  if (context.isWaiting(LoadDataAction)) {
    return CircularProgressIndicator();
  }

  // Check if an action failed
  if (context.isFailed(LoadDataAction)) {
    var exception = context.exceptionFor(LoadDataAction);
    return Text('Error: ${exception?.message}');
  }

  // Show the data
  return Text('Data: ${context.state.data}');
}

Available methods:

  • context.isWaiting(ActionType)
    - Returns true if the action is in progress
  • context.isFailed(ActionType)
    - Returns true if the action recently failed
  • context.exceptionFor(ActionType)
    - Gets the exception from a failed action
  • context.clearExceptionFor(ActionType)
    - Manually clears the stored exception

Widget Selectors Pattern

For complex selection logic, create a

WidgetSelect
class to organize reusable selectors:

class WidgetSelect {
  final BuildContext context;
  WidgetSelect(this.context);

  // Getter shortcuts
  List<Item> get items => context.select((state) => state.items);
  User get currentUser => context.select((state) => state.user);

  // Custom finder methods
  Item? findById(int id) => context.select(
    (state) => state.items.firstWhereOrNull((item) => item.id == id)
  );

  List<Item> searchByText(String text) => context.select(
    (state) => state.items.where((item) => item.name.contains(text)).toList()
  );
}

Add it to your BuildContext extension:

extension BuildContextExtension on BuildContext {
  AppState get state => getState<AppState>();
  // ... other methods ...
  WidgetSelect get selector => WidgetSelect(this);
}

Usage in widgets:

Widget build(BuildContext context) {
  var user = context.selector.currentUser;
  var item = context.selector.findById(42);
  return Text('${user.name}: ${item?.name}');
}

Important Guidelines

Avoid context.state inside selectors

Never use

context.state
inside your selector functions. This defeats the purpose of selective rebuilding:

// WRONG - rebuilds on any state change
var items = context.select((state) => context.state.items.where(...));

// CORRECT - only rebuilds when items change
var items = context.select((state) => state.items.where(...));

Never nest context.select calls

Nesting

context.select
causes errors. Always apply selection at the top level:

// WRONG - will cause errors
var result = context.select((state) =>
  context.select((s) => s.items).where(...) // Nested select!
);

// CORRECT
var items = context.select((state) => state.items);
var result = items.where(...);

Debugging Rebuilds

To observe when widgets rebuild (useful for performance debugging), use a

ModelObserver
:

var store = Store<AppState>(
  initialState: AppState.initialState(),
  modelObserver: DefaultModelObserver(),
);

The

DefaultModelObserver
logs console output showing:

  • Whether a rebuild occurred
  • Which connector/widget triggered it
  • The view model state

Example output:

Model D:1 R:1 = Rebuild:true, Connector:MyWidgetConnector, Model:MyViewModel{counter: 5}

References

URLs from the documentation: