hono-preact
Overview
Quick Start
The Route Table
Layouts & Nesting
Adding Pages
Active Links
Server Loaders
Loading States
Reloading Data
Prefetching
Streaming
Live Loaders
Realtime Channels
Server Actions
Validation
Optimistic UI
View Transitions
Middleware
CSRF Protection
CLI
Vite Config
Project Structure
Composing Hono Middleware
WebSockets
Rooms & Presence
renderPage
Link Prefetch
Build & Deploy
Overview
Dialog
Popover
Tooltip
Menu
Context Menu
Select
Combobox
Toast
renderElement
useControllableState
mergeRefs
useListNavigation
useTypeahead
useListboxSelection
usePosition
usePositioner
useDismiss
useFocusReturn
useSafeArea
usePresence

useControllableState#

useControllableState lets a single component support both controlled and uncontrolled use from one hook. When the caller passes a value, the component is controlled and the value is the single source of truth; when they pass only a default, the component manages its own state. Either way, the setter is stable across renders, so you can list it in effect dependencies without re-subscribing.

Demo#

Example#

A toggle that works controlled or uncontrolled, mirroring how Dialog.Root handles open / defaultOpen / onOpenChange:

import { useControllableState } from 'hono-preact-ui';

type ToggleProps = {
  pressed?: boolean; // controlled
  defaultPressed?: boolean; // uncontrolled
  onPressedChange?: (pressed: boolean) => void;
};

export function Toggle({
  pressed,
  defaultPressed,
  onPressedChange,
}: ToggleProps) {
  const [on, setOn] = useControllableState<boolean>({
    value: pressed,
    defaultValue: defaultPressed ?? false,
    onChange: onPressedChange,
  });

  return (
    <button type="button" aria-pressed={on} onClick={() => setOn(!on)}>
      {on ? 'On' : 'Off'}
    </button>
  );
}
// Uncontrolled: the component owns the state.
<Toggle defaultPressed onPressedChange={(p) => console.log(p)} />;

// Controlled: you own the state, the component reflects it.
const [pressed, setPressed] = useState(false);
<Toggle pressed={pressed} onPressedChange={setPressed} />;

The mode is assumed fixed for the component's lifetime, switching controlled and uncontrolled does not re-seed internal state, which matches typical usage.

Signature#

import { useControllableState } from 'hono-preact-ui';

function useControllableState<T>(opts: {
  value?: T; // controlled; when defined, the component is controlled
  defaultValue: T; // uncontrolled initial value
  onChange?: (value: T) => void;
}): [T, (next: T) => void];

When value is defined the hook is controlled: it reads value and never updates internal state, so the parent must apply the change. When value is absent it is uncontrolled, seeded from defaultValue. The setter always calls onChange.

Options#

OptionTypeDescription
valueTControlled value. When defined, the hook is controlled.
defaultValueTInitial value when uncontrolled.
onChange(value: T) => voidCalled with the next value on every change.

Returns [value, setValue]: the current value and a stable setter.