Skip to content

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,
});
  • getter receives a track() callback — call it to establish a reactive dependency for readers of .value.
  • setter receives the new value and a trigger() callback — call it to notify dependents. You decide when (immediately, after a delay, or never).
  • CustomRef also exposes trigger() directly for external notifications, and .raw for 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 dependenciestriggers updates
refautomatically on .value readautomatically on .value write
computedautomatically from the getterautomatically when inputs change
customRefwhen you call track()when you call trigger()

Released under the MIT License.