kamod-hooks · Preact-first · Typed · Tree-shakeableInstall · Compose · Explore
Ship Preact features faster with production-ready hooks
A Preact-first hook library inspired by ahooks — state, lifecycle, browser, and async helpers with zero runtime dependencies beyond Preact.
Start with one behavior your screen needs: a toggle, a counter or a browser preference. Keep the hook close to the component that owns it, then compose its state with your existing controls. Pair it with the component library when building your interface.
In this guide 9 sections
What the package brings
Reach for a hook when a component needs reusable behavior: a boolean state, a bounded counter or a browser preference. Keep the visual control in Kamod UI and the behavior in the hook.
Choose the right approach
Start with useToggle or useCounter and let the component own the state.
Avoid a shared store just to open one panel or change one quantity.
Use useLocalStorageState when a value should survive reloads.
Validate stored data and choose a safe default; persistence is not input validation.
Pick the dedicated hook after reviewing its lifecycle and options.
Check cleanup and stale results when the component unmounts or its inputs change.
Preact-first
Built for Preact hooks, not React-compat afterthoughts. Import what you need and keep bundles lean.
Start with a single component and let it own the hook's lifetime. This keeps independent instances independent and makes mounting, resetting and cleanup easier to reason about.
Tree-shakeable
ESM package with optional subpath imports like @kamod-ch/hooks/useToggle for tight production builds.
Choose an import convention your team can follow consistently. Check the production output before changing imports for bundle-size reasons; a smaller-looking import is not itself a measurement.
Demo-backed docs
Every hook ships with interactive demos and copy-ready source on the dedicated live docs site.
Use a demo to explore the normal path, then try the boundary that matters to your screen: an empty value, a fast repeated action or an unmount while work is pending.
Installation
Install @kamod-ch/hooks with Preact as a peer dependency when using the package outside the Kamod UI monorepo.
pnpm add @kamod-ch/hooks preactCheck your environment
Use your existing package manager and keep the peer dependencies aligned with your application. Compare package.json and your lockfile with the documentation for the version you install; add the package once at the workspace boundary that uses it.
Keep the first change small. Start with the usage example, run your project’s typecheck and build, then connect it to a real screen. The package overview helps you decide how companion libraries fit together.
Usage
Start with high-traffic hooks like useToggle, useCounter, and useLocalStorageState. Prefer named imports or subpath imports for tree-shaking.
import { useToggle, useCounter, useLocalStorageState } from "@kamod-ch/hooks";
export function useExampleState() {
const [on, { toggle }] = useToggle(false);
const [count, { inc }] = useCounter(0);
const [theme, setTheme] = useLocalStorageState("theme", { defaultValue: "dark" });
return { on, toggle, count, inc, theme, setTheme };
}Read the example
Follow the value from its definition to the interface. Each part below explains a decision to keep when adapting the example.
The first item describes what to render. The second contains operations that change it. Call the hook at the top level of a component or custom hook; call toggle from the event handler, not while rendering.
const [on, { toggle }] = useToggle(false);Put the bounds in the hook and reflect them in the controls. A disabled button explains the boundary visually, while the hook keeps programmatic changes inside the same range.
const [count, { inc, dec, reset }] = useCounter(1, { min: 1, max: 5 });The second argument is an options object. A default is used when no saved value exists; it is not a validation rule for old data. Check stored values before treating them as a restricted theme name.
const [theme, setTheme] = useLocalStorageState("theme", { defaultValue: "dark" });Connect a bounded quantity control
This example keeps behavior local and uses the same Kamod buttons as the rest of the interface. The disabled states explain the limits, and reset returns to the starting quantity. Render it twice to see that each instance owns its own count.
import { useCounter } from "@kamod-ch/hooks";
import { Button } from "@kamod-ch/ui";
export function Quantity() {
const [count, { inc, dec, reset }] = useCounter(1, { min: 1, max: 5 });
return (
<div role="group" aria-label="Quantity" class="flex flex-wrap items-center gap-2">
<Button variant="outline" disabled={count === 1} onClick={() => dec()}>Remove one</Button>
<output aria-live="polite">{count}</output>
<Button variant="outline" disabled={count === 5} onClick={() => inc()}>Add one</Button>
<Button variant="ghost" onClick={reset}>Reset</Button>
</div>
);
}Try the boundaries. Try the minimum, maximum and reset paths with a keyboard. When this becomes a cart control, send the intended quantity through your application boundary and handle server rejection separately; a local count is not proof of available stock.
Integrate with your application
Separate behavior from presentation. A hook can own the value or lifecycle while a button, field or dialog supplies the interface. Keep labels, disabled states and keyboard behavior in the component rather than recreating them inside each consumer.
Call hooks consistently at the top level of your component. Check each hook’s return types and cleanup behavior before combining it with an effect; avoid a second listener or subscription for work the hook already handles.
For browser-backed behavior, review the first server render and the first client render separately. Test storage access, viewport changes and unmounting in the environment where the component will actually run.
Choose the owner and lifetime
Start with the smallest owner that needs the behavior. Move a hook into a custom hook when several components need the same logic, not simply because a file is long. Sharing the function does not share its state: separate calls still represent separate instances. Lift ownership only when the product requires synchronized values.
Account for the environment
Browser hooks and server rendering have different timing. Keep direct access to window, document and storage out of server render paths. Prefer a stable initial render and apply browser-only information after mount. For asynchronous interactions, decide what the user sees while a request is pending and when a later request replaces it.
Connect the pieces. Persisted signals — Compare persistent, shared reactive values with component-owned hook state before choosing a second state owner.
When something behaves differently
Start with the smallest failing interaction. Compare a fresh page with the same page after a change or reload, and keep the package version in your reproduction.
- The value resets unexpectedly
- Check whether a changing key or a conditional parent unmounts the component. Local state belongs to that mounted instance; moving the visual control can change its lifetime.
- The saved preference has an unexpected type
- Inspect old storage values and your serializer. TypeScript describes current code, not historical browser data. Test fresh storage and an older value before deciding on a migration.
- An interaction repeats or finishes late
- Look for listeners, timers or requests created outside the hook's lifecycle. Verify cleanup when navigating away and rapid repeated input; do not hide the issue by suppressing all errors.
If the behavior still differs from the documented API, check existing issues before opening a report. Include the expected result, actual result, package version and a small reproduction without private application data.
API Reference
This page is a Kamod UI overview. The full API, categorized hook tables, and TypeScript signatures live on the dedicated kamod-hooks docs.
Use this page to understand the integration, then consult the Full API reference on live docs for exact signatures and supported options. Compare those details with the version in your lockfile before adapting an example.
Browse all 78 hooks with live demos
Open the full kamod-hooks docs for getting started, migration from ahooks, and every interactive demo.
- Before choosing an API: read its input types, return values and default behavior.
- Before shipping: review lifecycle or server-rendering notes for the features you use.
- When behavior differs: reduce the case to a small example and include your package version in the report.
Accessibility Notes
Pair hooks that drive UI (for example toggles, focus, and viewport observers) with clear labels, keyboard affordances, and semantic controls in your components.
Before you ship
- Exercise both sides of a toggle and the boundaries of counters.
- Unmount and remount the component; check that timers or listeners do not accumulate.
- Use semantic controls and expose state through visible labels and the appropriate ARIA attributes.
Review the result with real content and keyboard input. Keep visible labels, loading and error feedback, and focus behavior in sync with the state your application exposes.
Take the guide with you
Keep the examples, integration decisions and troubleshooting notes together in your project, or share them with a coding assistant. This reference works with any assistant; no model-specific setup is needed. Check the installed version and your project’s conventions before applying a suggestion.
Display changes only the view. Copy and download include the complete Markdown reference.
# Hooks — integration reference
A Preact-first hook library inspired by ahooks — state, lifecycle, browser, and async helpers with zero runtime dependencies beyond Preact.
Package: @kamod-ch/hooks
## Choose an approach
Reach for a hook when a component needs reusable behavior: a boolean state, a bounded counter or a browser preference. Keep the visual control in Kamod UI and the behavior in the hook.
### One component's interaction
Start with useToggle or useCounter and let the component own the state.
Avoid a shared store just to open one panel or change one quantity.
### A browser preference
Use useLocalStorageState when a value should survive reloads.
Validate stored data and choose a safe default; persistence is not input validation.
### Browser or asynchronous behavior
Pick the dedicated hook after reviewing its lifecycle and options.
Check cleanup and stale results when the component unmounts or its inputs change.
## Installation
Install @kamod-ch/hooks with Preact as a peer dependency when using the package outside the Kamod UI monorepo.
```bash
pnpm add @kamod-ch/hooks preact
```
## Starting example
```tsx
import { useToggle, useCounter, useLocalStorageState } from "@kamod-ch/hooks";
export function useExampleState() {
const [on, { toggle }] = useToggle(false);
const [count, { inc }] = useCounter(0);
const [theme, setTheme] = useLocalStorageState("theme", { defaultValue: "dark" });
return { on, toggle, count, inc, theme, setTheme };
}
```
### Separate the value from its actions
The first item describes what to render. The second contains operations that change it. Call the hook at the top level of a component or custom hook; call toggle from the event handler, not while rendering.
```tsx
const [on, { toggle }] = useToggle(false);
```
### Make the limits part of the behavior
Put the bounds in the hook and reflect them in the controls. A disabled button explains the boundary visually, while the hook keeps programmatic changes inside the same range.
```tsx
const [count, { inc, dec, reset }] = useCounter(1, { min: 1, max: 5 });
```
### Choose a default for an empty browser
The second argument is an options object. A default is used when no saved value exists; it is not a validation rule for old data. Check stored values before treating them as a restricted theme name.
```tsx
const [theme, setTheme] = useLocalStorageState("theme", { defaultValue: "dark" });
```
## Connect a bounded quantity control
This example keeps behavior local and uses the same Kamod buttons as the rest of the interface. The disabled states explain the limits, and reset returns to the starting quantity. Render it twice to see that each instance owns its own count.
File: src/components/Quantity.tsx
```tsx
import { useCounter } from "@kamod-ch/hooks";
import { Button } from "@kamod-ch/ui";
export function Quantity() {
const [count, { inc, dec, reset }] = useCounter(1, { min: 1, max: 5 });
return (
<div role="group" aria-label="Quantity" class="flex flex-wrap items-center gap-2">
<Button variant="outline" disabled={count === 1} onClick={() => dec()}>Remove one</Button>
<output aria-live="polite">{count}</output>
<Button variant="outline" disabled={count === 5} onClick={() => inc()}>Add one</Button>
<Button variant="ghost" onClick={reset}>Reset</Button>
</div>
);
}
```
Try the minimum, maximum and reset paths with a keyboard. When this becomes a cart control, send the intended quantity through your application boundary and handle server rejection separately; a local count is not proof of available stock.
## Ownership and environment
Start with the smallest owner that needs the behavior. Move a hook into a custom hook when several components need the same logic, not simply because a file is long. Sharing the function does not share its state: separate calls still represent separate instances. Lift ownership only when the product requires synchronized values.
Browser hooks and server rendering have different timing. Keep direct access to window, document and storage out of server render paths. Prefer a stable initial render and apply browser-only information after mount. For asynchronous interactions, decide what the user sees while a request is pending and when a later request replaces it.
A reusable interaction should preserve its behavior when the surrounding markup changes. Keep hook ownership stable while adjusting buttons, labels and layout.
## Troubleshooting
### The value resets unexpectedly
Check whether a changing key or a conditional parent unmounts the component. Local state belongs to that mounted instance; moving the visual control can change its lifetime.
### The saved preference has an unexpected type
Inspect old storage values and your serializer. TypeScript describes current code, not historical browser data. Test fresh storage and an older value before deciding on a migration.
### An interaction repeats or finishes late
Look for listeners, timers or requests created outside the hook's lifecycle. Verify cleanup when navigating away and rapid repeated input; do not hide the issue by suppressing all errors.
## Resources
- [Documentation](https://kamod-ch.github.io/kamod-hooks/)
- [Source](https://github.com/kamod-ch/kamod-hooks)
- [npm](https://www.npmjs.com/package/@kamod-ch/hooks)
## Attribution
Kamod Hooks is inspired by ahooks and adapts those patterns for Preact. Consult the package's LICENSE and NOTICE when redistributing source; compatibility and behavior should be checked against Kamod's own documentation.
[ahooks by Alibaba](https://github.com/alibaba/hooks)
Sources & attribution
This integration guide accompanies @kamod-ch/hooks, maintained in the Kamod ecosystem. The dedicated documentation is the reference for package APIs; examples here show how those APIs fit into a Kamod UI application.
Kamod Hooks is inspired by ahooks and adapts those patterns for Preact. Consult the package's LICENSE and NOTICE when redistributing source; compatibility and behavior should be checked against Kamod's own documentation. See ahooks by Alibaba for the original project.
When copying or distributing source, retain the applicable license and attribution notices from the version you use. Check the published package alongside your lockfile when comparing an example with a newer release.