Ordered lifecycle and state hooks for native Jaspr components.
jaspr_hooks brings the composability of flutter_hooks to Jaspr's own
component and element tree. It does not embed Flutter and produces normal Jaspr
HTML on the server and in the browser.
Current stable release:
0.1.2.
dependencies:
jaspr: ^0.23.3
jaspr_hooks: ^0.1.2The package supports Dart >=3.8.0 <4.0.0 and Jaspr 0.23.x.
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_hooks/jaspr_hooks.dart';
class Counter extends HookComponent {
const Counter({super.key});
@override
Component build(BuildContext context) {
final count = useState(0);
return button(
onClick: () => count.value++,
[Component.text('Count: ${count.value}')],
);
}
}Hooks must be called unconditionally and in the same order on every build.
Reusable custom hooks are ordinary functions whose names begin with use:
ValueNotifier<int> useTrackedCount([int initialValue = 0]) {
return useState(initialValue);
}For lower-level lifecycle objects, extend Hook<R> and
HookState<R, YourHook> and register them with use.
| Category | Hooks |
|---|---|
| Framework | use, useContext, useId, useEvent |
| Foundation | useLatest, useDisposable, useExternalStore, useInherited, useImperativeHandle, useTimeout, useInterval |
| Primitives | useState, useEffect, usePostFrameEffect, useMemoized, useCallback, useRef, useValueChanged |
| Lifecycle/state | usePrevious, useReducer, useIsMounted, useDebounced |
| Convenience state | useToggle, useCounter, useList, useMap, useSet, useQueue, useEffectOnce, useMount, useUnmount, useUpdateEffect |
| Async | useFuture, useStream, useStreamController, useOnStreamChange |
| Actions | useAsyncAction, useOptimistic |
| Listenables | useListenable, useListenableSelector, useValueNotifier, useValueListenable, useOnListenableChange |
| Browser lifecycle | useDocumentVisibility, useOnDocumentVisibilityChange, usePreferredColorScheme, useOnPreferredColorSchemeChange, useMediaQuery, useOnMediaQueryChange, usePreferredMotion, useOnPreferredMotionChange |
| Browser DOM | useNodeKey, useFocus, useFocusWithin, useActiveElement, useAbortController, useAnimationFrame, useHistoryState, useClipboard, useElementSize, useWindowSize, useIntersection, useEventListener, useOnClickOutside, useHover, useMutationObserver |
The universal entry point contains every hook except direct DOM integrations:
import 'package:jaspr_hooks/jaspr_hooks.dart';When a component needs DOM node, observer, or native-event types, import the web entry point instead. It re-exports the universal API:
import 'package:jaspr_hooks/web.dart';
import 'package:universal_web/web.dart' as web;Use HookComponent in place of StatelessComponent,
StatefulHookComponent when a Jaspr State is still useful, or HookBuilder
for a small hook-enabled subtree.
Jaspr renders a new component tree on the server and hydrates with a separate tree in the browser. Hook state is therefore not serialized automatically.
| API | Server/static rendering | Browser |
|---|---|---|
useEffect |
Effect is skipped | Runs synchronously during build |
usePostFrameEffect |
Effect is skipped | Runs after the frame |
useFuture, useStream, useOnStreamChange |
Source must be null |
Subscribes normally |
| Listenable hooks | Read initial value, attach no listener | Attach listeners |
useDebounced, useTimeout, useInterval |
Create no timer | Run timers normally |
useExternalStore |
Requires getServerSnapshot |
Hydrates from the server snapshot, then subscribes |
| Async and optimistic actions | Read deterministic initial state; do not mutate | Dispatch and mutate from event handlers |
| Browser lifecycle value hooks | Return unknown |
Synchronize after the first frame |
| Browser DOM value hooks | Return inert state, null, or an unattached key |
Attach after the first frame |
Pass async sources conditionally:
final future = useMemoized(
() => kIsWeb ? loadFromBrowser() : null,
const [],
);
final snapshot = useFuture(future);Use Jaspr's PreloadStateMixin, AsyncStatelessComponent, serialized
@client properties, or jaspr_riverpod for server-loaded data. Keep initial
state and memoized values deterministic so the server and first client build
produce compatible markup.
Use usePostFrameEffect for DOM reads or writes. useEffect intentionally
matches flutter_hooks synchronous client timing.
flutter_hooks cannot be imported by native Jaspr components because its
runtime is coupled to Flutter widgets and elements. This package adapts its
ordered-hook design to public Jaspr element APIs.
- Jaspr
Component,BuildContext,ValueNotifier,AsyncSnapshot, andConnectionStatereplace Flutter types. - Effects and live subscriptions are SSR-aware.
- Document visibility and preferred color scheme replace Flutter application lifecycle and platform brightness hooks.
- Flutter animation, ticker, scroll, focus, text editing, overlay, Material,
Cupertino, keep-alive, controller, and
reassemblehooks are intentionally absent. - The internal element mixin is not public;
HookandHookStateare the supported custom-hook extension points.
The example/ directory is a static Jaspr Content application with
one guide and interactive demo for every hook. It is also the source deployed
to GitHub Pages and displayed in pub.dev's Example tab.
The documentation home page includes an AI install prompt button that copies compatibility, installation, hook-rule, and SSR instructions for a coding agent.
The machine reference documents every exported hook and is checked against the canonical hook catalog and package version constraints in CI.
pub.dev releases are prepared by the repository's release checks and published manually from a clean local checkout.
jaspr_hooks is inspired by and partially adapted from
flutter_hooks, copyright
Remi Rousselet, under the MIT License. The exact source baseline and full
license are recorded in THIRD_PARTY_NOTICES.md.
This is an independent project. It is not maintained, sponsored, or endorsed
by the Jaspr or flutter_hooks projects. The fishing-dog artwork is an original
project mascot and is not derived from Jaspr's mascot.
The package itself is available under the MIT License in LICENSE.
