Skip to content

Untracked

By default, every read of a @SolidState field inside a reactive context — a build method, an @SolidEffect body, or an @SolidQuery body — registers a dependency. When the field changes, the surrounding computation re-runs: build re-renders the widget subtree at the read site, an effect re-fires, a query re-executes. That’s how fine-grained reactivity stays automatic.

Occasionally you need to read the current value without subscribing. Solid covers two cases.

Reads inside onPressed, onTap, onChanged, and other named-callback parameters starting with on are automatically untracked. Solid treats those as user-interaction handlers — they fire in response to gestures, not signal changes — so registering a dependency on every read inside them would be wrong.

source/conditional_dialog.dart
class ConditionalDialog extends StatelessWidget {
ConditionalDialog({super.key});
@SolidState()
int counter = 0;
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
if (counter > 10) showDialog<void>(/*...*/); // read is untracked
counter++; // writes are always untracked
},
child: const Icon(Icons.add),
);
}
}

You don’t write anything special — Solid handles it. Just keep in mind the body reads a snapshot, not a live value.

For one-off untracked reads outside a callback, append .untracked to the field reference. The most common case is reading a value once for key construction or imperative initialization, without rebuilding on later changes.

source/keyed_container.dart
class KeyedContainer extends StatelessWidget {
KeyedContainer({super.key});
@SolidState()
int counter = 0;
@override
Widget build(BuildContext context) {
return Container(
// ValueKey reads counter once; it does NOT rebuild on counter changes.
key: ValueKey(counter.untracked),
child: const Text('hi'),
);
}
}

.untracked is a typed identity extension shipped by solid_annotationscounter.untracked has the same type as counter and at runtime is a no-op. Only the source-time presence matters: the generator rewrites counter.untracked to the underlying untrackedValue primitive and excludes the read from the dependency set, so SignalBuilder doesn’t wrap it inside build and an enclosing effect or query won’t re-fire on changes to it.

  • Building a one-time Key / ValueKey from a reactive field.

  • Inside a @SolidEffect that writes to a signal, reading the same signal’s current value to avoid a self-dependency loop:

    @SolidEffect()
    void recordHistory() {
    history = [...history.untracked, counter]; // counter is tracked, history is not
    }
  • Logging / analytics calls that should not cause rebuilds.

If you find yourself sprinkling .untracked everywhere, step back — usually a @SolidState getter (derived state) or a non-reactive plain field is the better tool.