Custom Refs
customRef gives you full manual control over when a ref tracks dependencies and when it triggers updates — the same idea as Vue's customRef.
Use it when the built-in ref / computed reactivity doesn't fit:
- Wrapping external state systems (like
Listenable) into reactivity - Debouncing or throttling updates
- Custom validation or transformation logic
API
dart
CustomRef<T> customRef<T>({
required T Function(void Function() track) getter,
required void Function(T value, void Function() trigger) setter,
});getterreceives atrack()callback — call it to establish a reactive dependency for readers of.value.setterreceives the new value and atrigger()callback — call it to notify dependents. You decide when (immediately, after a delay, or never).CustomRefalso exposestrigger()directly for external notifications, and.rawfor untracked reads.
For read-only wrapping (no setter), use ReadonlyCustomRef:
dart
final custom = ReadonlyCustomRef<MyModel>(
getter: (track) {
track();
return model;
},
);Example — debounced ref
dart
CustomRef<String> debouncedRef(String initial, Duration delay) {
var internalValue = initial;
Timer? timer;
return customRef<String>(
getter: (track) {
track();
return internalValue;
},
setter: (value, trigger) {
internalValue = value;
timer?.cancel();
timer = Timer(delay, () {
trigger(); // Only notify dependents after the delay
});
},
);
}Example — wrapping a Listenable
dart
final notifier = ValueNotifier<int>(0);
final custom = ReadonlyCustomRef<ValueNotifier<int>>(
getter: (track) {
track();
return notifier;
},
);
notifier.addListener(custom.trigger);
// Reactive: recomputes whenever the notifier fires
final current = computed(() => custom.value.value);TIP
For Listenable / ChangeNotifier wrapping, prefer the built-in manageListenable / manageChangeNotifier composables — they implement exactly this pattern with lifecycle cleanup included.
Relationship to ref and computed
| tracks dependencies | triggers updates | |
|---|---|---|
ref | automatically on .value read | automatically on .value write |
computed | automatically from the getter | automatically when inputs change |
customRef | when you call track() | when you call trigger() |