action#
function |
Package: package:preact_signals
Function: action#
Function action(Function fn)
Wraps a callback function into a reusable, batched, and untracked action.
An action is a higher-order function that takes a callback and returns a new function with the exact same signature. When the returned function is executed, it runs the original callback inside both a batch and an untracked block.
Why use action instead of batch?#
-
Reusability:
batch(fn)executes the callback immediately. In contrast,action(fn)returns a reusable function that you can store, pass around, and invoke multiple times to perform batch transactions on demand. -
Untracked Execution: The callback runs inside
untracked. If you invoke the action from within aneffector acomputedsignal, the outer reactive context will not establish subscriptions to any signals read inside the action.
Example: Comparing Normal Updates vs. Action Batching#
Without Actions (Standard Sequential Updates)
Every signal write immediately notifies active subscribers. This causes transient states and redundant, intermediate executions:
import 'package:preact_signals/preact_signals.dart';
final a = signal('a');
final b = signal('b');
void main() {
// Set up a subscriber effect
effect(() => print('${a.value} ${b.value}'));
// Prints immediately: "a b"
a.value = 'aa'; // Prints: "aa b"
b.value = 'bb'; // Prints: "aa bb"
}
Total prints: 3 (initial execution + 2 updates).
With Actions (Coalesced Transaction)
By wrapping the state-mutating function in action, all updates are postponed and flushed in a single notification block once the function completes:
import 'package:preact_signals/preact_signals.dart';
final a = signal('a');
final b = signal('b');
// Create a reusable action
final updateFields = action((String nextA, String nextB) {
a.value = nextA;
b.value = nextB;
});
void main() {
effect(() => print('${a.value} ${b.value}'));
// Prints immediately: "a b"
updateFields('aa', 'bb');
// The effect is deferred during execution and triggers exactly once at the end.
// Prints: "aa bb"
}
Total prints: 2 (initial execution + 1 coalesced update).
Type-Safety & Extensions#
While action accepts any generic Function, Dart's static analysis benefits greatly from
type-safe variants or extensions.
-
Type-safe functions: Use
action0throughaction10(e.g.action2(...)for 2 arguments) to preserve type arguments. -
Extensions: Call
.actiondirectly on any Dart function (e.g.,myFunction.action).
References#
The action type is referenced and used in the following pages:
- Signals.dart
- Signal (signals_flutter/core)
- Action (signals_flutter/core)
- MapSignalMixin (signals_flutter/mixins)
- QueueSignalMixin (signals_flutter/mixins)
- IterableSignalMixin (signals_flutter/mixins)
- StreamSignalMixin (signals_flutter/mixins)
- AsyncSignal (signals_flutter/async)
- Model (signals_flutter/utilities)
- signals_flutter
- SignalProvider (signals_flutter/widgets)
- Signal (signals_core/core)
- Action (signals_core/core)
- MapSignalMixin (signals_core/mixins)
- QueueSignalMixin (signals_core/mixins)
- IterableSignalMixin (signals_core/mixins)
- StreamSignalMixin (signals_core/mixins)
- AsyncSignal (signals_core/async)
- Model (signals_core/utilities)
- signals_core
- Signal (signals/core)
- Action (signals/core)
- MapSignalMixin (signals/mixins)
- QueueSignalMixin (signals/mixins)
- IterableSignalMixin (signals/mixins)
- StreamSignalMixin (signals/mixins)
- AsyncSignal (signals/async)
- Model (signals/utilities)
- signals
- SignalProvider (signals/widgets)
- Action (preact_signals/core)
- Model (preact_signals/utilities)
- preact_signals