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.