Represents a mutable reactive state container that sits at the foundation of the reactivity system.
Signals hold a single, mutable value that can be read or modified. When a signal's value is updated, any active computations (like Computed) or effects (like effect) that read the signal's value inside their execution context are automatically notified and scheduled to re-run.
Under the hood, this establishes a reactive dependency graph where reading a signal registers the reader as a "target", and updating a signal triggers direct, glitch-free propagation to all registered targets.
.value inside a reactive context (like effect or computed) registers a dependency. Reading a value
outside a reactive context behaves like a standard getter without creating a subscription.
Example Usage#
1. Basic Reactive Flow
import 'package:preact_signals/preact_signals.dart';
void main() {
final count = Signal(0);
// The effect automatically subscribes to count.value
effect(() {
print('Count is: ${count.value}');
});
count.value = 5; // Triggers print: Count is: 5
}
2. Controlling Subscriptions via .peek()
If you need to read a signal's value without subscribing to updates, use the .peek() method:
final count = Signal(0);
final threshold = Signal(10);
effect(() {
// Subscribes to count, but NOT to threshold
if (count.value >= threshold.peek()) {
print('Threshold reached!');
}
});
Constructors#
View Constructors
Signal(this._internalValue, {String? name, void Function()? watched, void Function()? unwatched, ReadonlySignalOptions? options, SignalEquality? equality})
Creates a new Signal instance with the given initial value.
You can optionally provide:
- A name for debugging/observer tracing.
- watched/unwatched hooks triggered when the signal gains its first subscriber or loses its last subscriber.
- equality checking callback to customize how value modifications are compared.
final count = Signal(0, name: 'counter_signal');
Signal.lazy({String? name, void Function()? watched, void Function()? unwatched, ReadonlySignalOptions? options, SignalEquality? equality})
Creates a new lazy Signal instance that is computed on-demand upon first read.
.value = ... or .set(...)
will throw a runtime initialization exception.
final lazyUser = Signal<User>.lazy(name: 'lazy_user');
// Throws error:
// print(lazyUser.value);
lazyUser.value = User(id: 1, name: 'John'); // Initialized successfully
print(lazyUser.value); // Safe to read now
Properties#
View Properties
int globalId
String? name
void Function()? watched
void Function()? unwatched
int version
Version numbers should always be >= 0, because the special value -1 is used by Nodes to signify potentially unused but recyclable nodes.
Methods#
View Methods
SignalEquality equalityCheck
Get the active equality check
bool isInitialized
Check if the value is set and not a lazy signal
T internalValue
bool internalRefresh()
void subscribeToNode(Node node)
void unsubscribeFromNode(Node node)
void Function() subscribe(void Function(T value) fn)
T value
Gets the current value of the signal.
If read inside an active reactive context (e.g., an effect or computed signal), the calling context automatically subscribes to updates of this signal.
value(T val)
Sets the current value of the signal.
If the new value is not equal to the existing value (based on equalityCheck), the signal's version is incremented and all active downstream subscribers (computeds/effects) are synchronously notified to re-evaluate.
bool set(T val, {bool force = false})
Updates the signal's value by method call.
Under normal conditions, this only notifies subscribers if the new value is different from the current value.
Set force to true to bypass standard equality checks and notify downstream subscribers
unconditionally. This is useful when working with mutable collections or class instances where
properties change in-place but the object reference remains identical.
final numbers = Signal([1, 2, 3]);
numbers.value.add(4); // In-place modification
numbers.set(numbers.value, force: true); // Force notify downstream subscribers
ReadonlySignalOptions#
Configuration options for a ReadonlySignal.
Allows intercepting the signal's active subscription state changes via watched and unwatched callback event listeners. This is extremely useful for initiating or canceling active background fetching, web sockets, or timer loops.
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final stockTicker = signal(
0.0,
options: ReadonlySignalOptions(
name: 'stock-ticker',
watched: () => print('Stock Ticker is actively being listened to!'),
unwatched: () => print('No more listeners, sleeping the ticker.'),
),
);
Constructors#
View Constructors
ReadonlySignalOptions({super.name, this.watched, this.unwatched})
Creates a new ReadonlySignalOptions instance.
Properties#
View Properties
void Function()? watched
Callback called when the signal goes from 0 to >=1 listeners.
void Function()? unwatched
Callback called when the signal goes from >=1 to 0 listeners.
Methods#
View Methods
ReadonlySignalOptions copyWith({String? name, void Function()? watched, void Function()? unwatched})
Creates a copy of this options with custom overrides.
bool ==(Object other)
int hashCode
ComputedOptions#
Configuration options for a Computed signal.
Enables configuring debugging names and subscription state event listeners for computed derivations.
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final count = signal(0);
final doubleCount = computed(
() => count.value * 2,
options: ComputedOptions(
name: 'double-count',
watched: () => print('Computed doubleCount is active'),
unwatched: () => print('Computed doubleCount is inactive'),
),
);
Constructors#
View Constructors
ComputedOptions({super.name, super.watched, super.unwatched})
Creates a new ComputedOptions instance.
Methods#
View Methods
ComputedOptions copyWith({String? name, void Function()? watched, void Function()? unwatched})
bool ==(Object other)
int hashCode
SignalOptions#
Configuration options for a Signal.
Extends ReadonlySignalOptions to also support custom equality checkers, which control whether incoming values trigger update events.
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final items = signal(
[1, 2, 3],
options: SignalOptions(
name: 'item-list',
equality: SignalEquality.deep(),
watched: () => print('Items watch active'),
unwatched: () => print('Items watch inactive'),
),
);
Constructors#
View Constructors
SignalOptions({super.name, super.watched, super.unwatched, SignalEquality? equality})
Creates a new SignalOptions instance.
Methods#
View Methods
SignalEquality equalityCheck
Get the active equality check
SignalOptions copyWith({String? name, void Function()? watched, void Function()? unwatched})
bool ==(Object other)
int hashCode
EffectOptions#
Configuration options for reactive Effects.
Permits naming the effect for debugging, performance profiling, and tracing within the signals developer tools.
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final count = signal(0);
final logger = effect(
() => print('Count changed to: ${count.value}'),
options: const EffectOptions(name: 'counter-logger'),
);
Constructors#
Methods#
View Methods
EffectOptions copyWith({String? name})
Creates a copy of this options with custom overrides.
bool ==(Object other)
int hashCode
signal#
Convenient global constructor for creating a mutable reactive state signal.
Example Usage#
import 'package:preact_signals/preact_signals.dart';
final count = signal(0);
final name = signal('Jane');
SignalOptionsBase#
Base configuration options for reactive components and signals.
Contains common options across all signals, computed values, and effects, such as the debug name.
Constructors#
Properties#
Methods#
SignalEffectException#
Error for when a effect fails to run the callback
Constructors#
View Constructors
SignalEffectException(this.error, [this.stackTrace])
Error for when a effect fails to run the callback
Properties#
View Properties
Object? error
Error during callback
StackTrace? stackTrace
StackTrace for where the error started