Skip to content

Widgets

The flutter_reacton package provides four specialized widgets for different reactive UI patterns. Each serves a distinct purpose -- use the decision matrix at the bottom to choose the right one.

ReactonBuilder

A StatefulWidget that subscribes to a single reacton and rebuilds its builder when the value changes.

API

dart
class ReactonBuilder<T> extends StatefulWidget {
  const ReactonBuilder({
    Key? key,
    required ReactonBase<T> reacton,
    required Widget Function(BuildContext context, T value) builder,
  });
}
ParameterTypeDescription
reactonReactonBase<T>The reacton to watch
builderWidget Function(BuildContext, T)Called with the current value whenever it changes

Example

dart
ReactonBuilder<int>(
  reacton: counterReacton,
  builder: (context, count) => Text('Count: $count'),
)

When to Use

Use ReactonBuilder when you want to isolate rebuilds to a specific subtree for a single reacton. It is more explicit than context.watch() and makes the subscription boundary visible in the widget tree.

dart
// Only the Text rebuilds when counterReacton changes,
// not the entire Scaffold
Scaffold(
  appBar: AppBar(title: const Text('My App')),
  body: ReactonBuilder<int>(
    reacton: counterReacton,
    builder: (context, count) => Text('$count'),
  ),
)

ReactonConsumer

A StatefulWidget that provides a ReactonWidgetRef to its builder, allowing you to watch multiple reactons within a single widget boundary.

API

dart
class ReactonConsumer extends StatefulWidget {
  const ReactonConsumer({
    Key? key,
    required Widget Function(BuildContext context, ReactonWidgetRef ref) builder,
  });
}
ParameterTypeDescription
builderWidget Function(BuildContext, ReactonWidgetRef)Called with a ref that can watch any number of reactons

ReactonWidgetRef

The ref object passed to the builder provides these methods:

MethodSignatureDescription
watchT watch<T>(ReactonBase<T> reacton)Subscribe to a reacton (rebuilds on change)
readT read<T>(ReactonBase<T> reacton)One-time read (no subscription)
setvoid set<T>(WritableReacton<T> reacton, T value)Write a value
updatevoid update<T>(WritableReacton<T> r, T Function(T) updater)Functional update
storeReactonStore get storeDirect store access

Example

dart
ReactonConsumer(
  builder: (context, ref) {
    final count = ref.watch(counterReacton);
    final name = ref.watch(nameReacton);
    final isEven = ref.watch(isEvenReacton);

    return Column(
      children: [
        Text('$name: $count'),
        Text(isEven ? 'Even' : 'Odd'),
        ElevatedButton(
          onPressed: () => ref.update(counterReacton, (c) => c + 1),
          child: const Text('Increment'),
        ),
      ],
    );
  },
)

When to Use

Use ReactonConsumer when you need to:

  • Watch multiple reactons in a single rebuild boundary
  • Keep read and write operations co-located in the same builder
  • Conditionally watch reactons based on other reacton values

TIP

ReactonConsumer re-creates subscriptions on every build to support conditional watches. If the set of watched reactons changes between builds, the old subscriptions are cleaned up and new ones are created automatically.

ReactonListener

A StatefulWidget that listens to a reacton for side effects without rebuilding its child. The child widget is passed through unchanged.

API

dart
class ReactonListener<T> extends StatefulWidget {
  const ReactonListener({
    Key? key,
    required ReactonBase<T> reacton,
    required void Function(BuildContext context, T value) listener,
    bool Function(T previous, T current)? listenWhen,
    required Widget child,
  });
}
ParameterTypeDescription
reactonReactonBase<T>The reacton to listen to
listenervoid Function(BuildContext, T)Called when the value changes
listenWhenbool Function(T previous, T current)?Optional filter: listener is called only when this returns true
childWidgetChild widget (never rebuilt by the listener)

Example

dart
ReactonListener<String?>(
  reacton: errorReacton,
  listener: (context, error) {
    if (error != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(error)),
      );
    }
  },
  child: const MyPageContent(),
)

Conditional Listening with listenWhen

Use listenWhen to filter which changes trigger the listener:

dart
ReactonListener<int>(
  reacton: counterReacton,
  listenWhen: (previous, current) => current > 10,
  listener: (context, count) {
    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: Text('Count exceeded 10! Current: $count'),
      ),
    );
  },
  child: const CounterPage(),
)

When to Use

Use ReactonListener for side effects that should not cause widget rebuilds:

  • Showing snackbars, dialogs, or bottom sheets
  • Navigation
  • Analytics events
  • Logging

ReactonSelector

A StatefulWidget that watches a reacton but only rebuilds when a selected sub-value changes. More efficient than ReactonBuilder when you need only a small portion of a complex state object.

API

dart
class ReactonSelector<T, S> extends StatefulWidget {
  const ReactonSelector({
    Key? key,
    required ReactonBase<T> reacton,
    required S Function(T value) selector,
    required Widget Function(BuildContext context, S selected) builder,
  });
}
ParameterTypeDescription
reactonReactonBase<T>The source reacton
selectorS Function(T)Extracts the sub-value from the full value
builderWidget Function(BuildContext, S)Called only when the selected sub-value changes

Example

dart
ReactonSelector<User, String>(
  reacton: userReacton,
  selector: (user) => user.name,
  builder: (context, name) => Text('Hello, $name'),
)

In this example, if userReacton changes but user.name stays the same (e.g., only user.email changed), the widget does not rebuild.

When to Use

Use ReactonSelector when:

  • You have a complex state object (e.g., a User model with many fields)
  • Your widget only depends on one or a few fields
  • You want to avoid unnecessary rebuilds from unrelated field changes

TIP

The selector uses != to compare the previous and current selected values. For custom types, make sure to implement == and hashCode or use the options.equals parameter on the underlying reacton.

Decision Matrix

Use this table to choose the right widget for your use case:

Use CaseWidgetWhy
Display a single reacton's valueReactonBuilderSimple, explicit subscription boundary
Display values from multiple reactonsReactonConsumerSingle builder can watch many reactons
Inline reacton access in any widgetcontext.watch()Least boilerplate, no extra widget needed
Side effects (snackbar, navigation)ReactonListenerDoes not rebuild child
Watch a sub-value of a complex reactonReactonSelectorPrevents rebuilds from unrelated changes
Read a value without rebuildingcontext.read()No subscription, use in event handlers

When to Prefer context.watch() Over Widgets

For most cases, context.watch() is the simplest approach:

dart
// Simple and effective
@override
Widget build(BuildContext context) {
  final count = context.watch(counterReacton);
  return Text('$count');
}

Use the dedicated widgets when you need:

  • Explicit rebuild boundaries in a large widget tree (ReactonBuilder)
  • Side effect callbacks without rebuilds (ReactonListener)
  • Sub-value filtering for complex state (ReactonSelector)
  • Multi-reacton ref-based access in a single boundary (ReactonConsumer)

Composing Widgets

These widgets can be freely composed and nested:

dart
ReactonListener<String?>(
  reacton: errorReacton,
  listener: (context, error) {
    if (error != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(error)),
      );
    }
  },
  child: ReactonConsumer(
    builder: (context, ref) {
      final count = ref.watch(counterReacton);
      final name = ref.watch(nameReacton);
      return Column(
        children: [
          Text('$name: $count'),
          ReactonSelector<User, String>(
            reacton: userReacton,
            selector: (user) => user.avatarUrl,
            builder: (context, url) => CircleAvatar(
              backgroundImage: NetworkImage(url),
            ),
          ),
        ],
      );
    },
  ),
)

What's Next

Released under the MIT License.