duxt_signals 0.1.0
duxt_signals: ^0.1.0 copied to clipboard
Reactive signals for Dart - lightweight state management inspired by Solid.js, Angular, and Preact Signals.
duxt_signals #
Reactive signals for Dart - lightweight state management inspired by Solid.js, Angular, and Preact Signals.
Features #
- Signals - Reactive state primitives with simple read/write API
- Computed - Derived state with automatic dependency tracking
- Effects - Side effects that run when signals change
- Batch - Batch multiple updates for optimal performance
- Form Signals - Form state management with validation
- Jaspr Integration - Hooks and components for Jaspr apps
Installation #
dependencies:
duxt_signals: ^0.1.0
Quick Start #
import 'package:duxt_signals/duxt_signals.dart';
// Create a signal
final count = signal(0);
// Read value (call syntax)
print(count()); // 0
// Write value
count.set(5);
count.update((v) => v + 1);
// Computed signals
final doubled = computed(() => count() * 2);
print(doubled()); // 12
// Effects
final dispose = effect(() {
print('Count is: ${count()}');
});
count.set(10); // Prints: "Count is: 10"
dispose(); // Stop effect
Core API #
signal() #
Creates a reactive signal with an initial value.
final name = signal('John');
final age = signal(25);
final items = signal<List<String>>([]);
// Read
print(name()); // Call syntax (preferred)
print(name.value); // Property syntax
// Write
name.set('Jane');
name.value = 'Jane'; // Alternative
// Update based on previous
age.update((v) => v + 1);
items.update((list) => [...list, 'new']);
// Listen
final unsubscribe = name.listen((value) {
print('Name changed to: $value');
});
unsubscribe(); // Stop listening
computed() #
Creates a derived signal that auto-updates when dependencies change.
final firstName = signal('John');
final lastName = signal('Doe');
final fullName = computed(() => '${firstName()} ${lastName()}');
print(fullName()); // "John Doe"
firstName.set('Jane');
print(fullName()); // "Jane Doe" - auto updated!
effect() #
Runs a side effect when signals change.
final count = signal(0);
final dispose = effect(() {
print('Count: ${count()}');
// DOM updates, API calls, etc.
});
count.set(1); // Prints: "Count: 1"
dispose(); // Stop effect
batch() #
Batch multiple signal updates to prevent redundant computations.
final a = signal(1);
final b = signal(2);
final sum = computed(() => a() + b());
// Without batch: sum recomputes twice
a.set(10);
b.set(20);
// With batch: sum recomputes once
batch(() {
a.set(10);
b.set(20);
});
Form Signals #
Form state management with validation.
final email = formField('', validators: [
required('Email is required'),
email('Invalid email'),
]);
final password = formField('', validators: [
required('Password is required'),
minLength(8, 'At least 8 characters'),
]);
// Use in UI
DInput(
value: email(),
onInput: email.set,
error: email.error,
);
// Check validity
if (email.isValid && password.isValid) {
submit();
}
FormState #
Group multiple fields together.
final form = FormState({
'email': formField('', validators: [required(), email()]),
'password': formField('', validators: [required()]),
});
// Access values
print(form.values); // {'email': '...', 'password': '...'}
// Check validity
if (form.isValid) {
form.submit((data) => api.login(data));
}
// Reset all fields
form.reset();
Jaspr Integration #
Watch Component #
Rebuilds when signals change.
Watch(() => div([
Text('Count: ${count()}'),
button(onClick: () => count.update((v) => v + 1), [Text('+')]),
]));
Documentation #
Full documentation at duxt.dev/duxt-signals
License #
MIT License - see LICENSE