agentskills.codes
US

use-jolt-setup

Use when building Flutter widgets in a setup-once / hooks-driven composition style — declaring signals, controllers, listenables, effects, timers, and lifecycle callbacks through `use*` hooks inside a `setup` function that runs once, owns and disposes those resources, and returns a builder for react

Install

mkdir -p .claude/skills/use-jolt-setup && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16252" && unzip -o skill.zip -d .claude/skills/use-jolt-setup && rm skill.zip

Installs to .claude/skills/use-jolt-setup

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use when building Flutter widgets in a setup-once / hooks-driven composition style — declaring signals, controllers, listenables, effects, timers, and lifecycle callbacks through `use*` hooks inside a `setup` function that runs once, owns and disposes those resources, and returns a builder for reactive rebuilds. Provides SetupWidget, SetupMixin (on existing StatefulWidget), and SetupBuilder.
394 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)

About this skill

Use Jolt Setup

package:jolt_setup/jolt_setup.dart brings a Vue/React-style composition model to Flutter on top of Jolt. A widget has two phases:

  1. Setup phase — a setup function runs once per widget identity. Inside it, use* hooks declare every long-lived resource: signals, controllers, listenables, effects, timers, lifecycle callbacks. The setup function returns a WidgetFunction (() => Widget) that closes over those resources.
  2. Build phase — the returned builder is invoked on every rebuild. Reactive reads (signal.value, controller.value, etc.) inside the builder are tracked through a PostFrameEffect, so changes coalesce to one rebuild at frame end.

The package re-exports package:jolt_flutter/jolt_flutter.dart, so Signal, Computed, Effect, JoltBuilder, PostFrameEffect, .listenable / .notifier, etc. come along.

All hooks live in references/hooks.md. This file focuses on the three entry surfaces that own a setup runtime: SetupWidget, SetupMixin, and SetupBuilder.

Choosing an Entry Surface

SituationUse
New widget; want a dedicated immutable widget type and reactive propsSetupWidget<T>
Existing StatefulWidget whose State<T> you must keep (custom methods, mixins, external API)SetupMixin<T>
Local composition, prototypes, leaf widgets — no need for a named widget classSetupBuilder

All three share the same setup model below; they only differ in how they bind a setup callback to the Flutter element tree.

The Setup Runtime (shared by all three)

Every entry surface creates a SetupContext that:

  • Holds an EffectScope so signals/effects created during setup are automatically disposed when the widget unmounts.
  • Records the ordered list of hooks registered through useHook / use*. Hot reload reuses hooks by sequence position and runtime type; a mismatch unmounts the tail and re-mounts new hooks.
  • Owns a PostFrameEffect "renderer" that wraps the returned builder. Reactive reads inside the builder track this effect, so dependency changes schedule markNeedsBuild at frame end.
  • Forwards Flutter element lifecycle (didUpdateWidget, didChangeDependencies, activate, deactivate, unmount, reassemble) to each hook, in registration order on activate, reverse order on deactivate/unmount.
  • Exposes resetSetup() — schedules a full setup rerun at frame end (tears down hooks + renderer, runs setup again). Use only when the initialization boundary itself must rebuild; ordinary state changes belong in signals/effects.

Implication: don't put conditional use* calls behind if / loops that can change between rebuilds, and don't call use* outside setup — the hook index must be stable. (Bare reactive reads through .value happen in the builder, not in setup itself, unless you want them as setup-time dependencies of an effect/computed.)

SetupWidget<T>

A new widget base class. Override setup(BuildContext context, Props<T> props); the returned WidgetFunction<T> is the builder. Props<T> is a reactive view of the current widget instance — read props() or props.value to subscribe to widget-field changes from the parent.

class CounterCard extends SetupWidget<CounterCard> {
  const CounterCard({
    super.key,
    required this.title,
    required this.initialValue,
  });

  final String title;
  final int initialValue;

  @override
  setup(context, props) {
    final count = useSignal(props().initialValue);
    final label = useComputed(() => '${props().title}: ${count.value}');

    onMounted(() => debugPrint('mounted: ${props().title}'));

    return () => GestureDetector(
      onTap: () => count.value++,
      child: Text(label.value),
    );
  }
}

Use SetupWidget when the component has a clear identity (constructor fields, public API) and parent updates to those fields should flow into setup-owned state through props.

SetupMixin<T> (on State<T>)

Mixes into an existing State<T> subclass. Override WidgetFunction<T> setup(BuildContext context); access widget fields via the mixin getter props (reactive) or widget (plain Flutter). All State lifecycle and instance methods remain available — the mixin only adds the setup runtime on top.

class _WelcomePanelState extends State<WelcomePanel>
    with SetupMixin<WelcomePanel> {
  late AnimationController controller;

  void show() => controller.forward();
  void hide() => controller.reverse();

  @override
  setup(context) {
    controller = useAnimationController(
      duration: const Duration(milliseconds: 250),
    );

    return () => Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        FadeTransition(opacity: controller, child: const FlutterLogo(size: 72)),
        Row(children: [
          FilledButton(onPressed: show, child: const Text('Show')),
          OutlinedButton(onPressed: hide, child: const Text('Hide')),
        ]),
      ],
    );
  }
}

Use SetupMixin when you must keep a custom State<T> — e.g. to expose instance methods to a GlobalKey, mix in other State mixins, satisfy a base class, or migrate an existing widget incrementally.

Important: setup runs on first build, not in initState, because hooks may read inherited widgets (Theme.of(context) via useInherited). Override initState only for work that does not depend on setup-owned state.

SetupBuilder

A SetupWidget<SetupBuilder> constructed from an inline callback. No widget class needed; the setup callback receives BuildContext (no typed props, since the widget identity is SetupBuilder itself).

SetupBuilder(
  setup: (context) {
    final count = useSignal(0);
    useEffect(() => debugPrint('count = ${count.value}'));

    return () => FilledButton(
      onPressed: () => count.value++,
      child: Text('Count: ${count.value}'),
    );
  },
)

Use SetupBuilder for local composition, reactive leaf widgets in larger build methods, prototypes, or anywhere the cost of declaring a SetupWidget subclass outweighs its benefit. Each SetupBuilder in a parent build is a separate widget identity and gets its own setup runtime.

Hooks at a Glance

Hooks are top-level use* functions (or use*.variant(...) factories) that must be called inside a setup function. They fall into a few buckets:

  • Reactive primitivesuseSignal (+ .lazy/.list/.map/.set/.iterable/.async), useComputed (+ .withPrevious/.writable/...), useEffect, usePostFrameEffect, useWatcher (+ .immediately/.once), useEffectScope, useUntil (+ .when/.changed).
  • Lifecycle callbacksonMounted, onUnmounted, onDidUpdateWidget, onDidChangeDependencies, onActivated, onDeactivated.
  • Context & memouseContext, useSetupContext, useInherited, useMemoized, useAutoDispose, useHook (+ SetupHook<T> for custom hooks).
  • Flutter controllers — text/scroll/page/tab/animation/focus/etc. (auto-disposed).
  • Listenable & sync bridgesuseValueNotifier, useChangeNotifier, useListen.{value,listenable,stream} (+ .watch), useSync.{from,bidi}.
  • AsyncuseFuture (+ .watch), useStream (+ .watch), useStreamController (+ .broadcast).
  • MiscuseTimer (+ .periodic), useAppLifecycleState, useAutomaticKeepAlive (+ .value), experimental useSetupReset family.

Full signatures, variants, lifecycle behavior, and examples are in references/hooks.md. To write your own hook — function-style, class-style (SetupHook<T>), or extension-style attached to a built-in creator like useSignal.myThing — see references/custom-hooks.md.

Rules

  • Call use* only at the top level of setup — never inside conditionals, loops, callbacks, or after early returns. The hook index must be stable across rebuilds.
  • Return only the () => Widget from setup. Don't return computed widgets directly; let the builder close over reactive state so rebuilds work.
  • Long-lived resources belong in hooks (useSignal, useMemoized, controller hooks); they auto-dispose. Don't new them in setup without a hook unless you also handle disposal via onUnmounted or an EffectScope.
  • Use props() (SetupWidget) or the props getter (SetupMixin) when setup-owned state must react to parent updates; raw widget.foo reads in setup are captured only at the first run.
  • resetSetup() is a heavy escape hatch (rebuilds the whole hook sequence). Prefer ordinary signal updates.
  • Reading inherited widgets inside setup: use useInherited((c) => Theme.of(c)) so dependency changes invalidate the cached value reactively. Plain Theme.of(context) works too but isn't reactive past the initial setup.

Source Navigation

Read source before answering API-sensitive questions.

AreaPath
Public exportspackages/jolt_setup/lib/jolt_setup.dart
SetupContext, SetupHook, lifecycle plumbingpackages/jolt_setup/lib/src/setup/framework.dart
SetupWidget, SetupBuilder, SetupWidgetElementpackages/jolt_setup/lib/src/setup/widget.dart
SetupMixinpackages/jolt_setup/lib/src/setup/stateful_mixin.dart
Built-in hookspackages/jolt_setup/lib/src/hooks/
Examplepackages/jolt_setup/example/jolt_setup_example.dart
Testspackages/jolt_setup/test/

Search skills

Search the agent skills registry