Skip to content

Hooks reference

Every signature on this page is read from the package’s own declarations when the site is built.

One per kind of entity, from @dashhaus/core. Each has its own page under Entities, with its options and what it returns.

useHaLight(entityId: string, options?: HaLightOptions): HaLightResult
useHaToggle(entityId: string, options?: HaToggleOptions): HaToggleResult
useHaCover(entityId: string, options?: HaCoverOptions): HaCoverResult
useHaSound(entityId: string, options?: HaSoundOptions): HaSoundResult
useHaClimate(entityId: string, options?: HaClimateOptions): HaClimateResult
useHaHumidifier(entityId: string, options?: HaHumidifierOptions): HaHumidifierResult
useHaFan(entityId: string, options?: HaFanOptions): HaFanResult
useHaSensor(entityId: string, options?: HaSensorOptions): HaSensorResult
useHaWeather(entityId: string, options?: HaWeatherOptions): HaWeatherResult
useHaGuestWifi(entityId: string, options?: HaGuestWifiOptions): HaGuestWifiResult
useHaAlarm(entityId: string, options?: HaAlarmOptions): HaAlarmResult
useHaLock(entityId: string, options?: HaLockOptions): HaLockResult
useHaOwlet(entityId: string, options?: HaOwletOptions): HaOwletResult

The provider and the hooks that read the store directly, from @dashhaus/core. Raw state and selectors shows when to use which.

HaProvider(options: HaProviderProps): Element

Own the store lifecycle at the application boundary, not inside a card.

storeHaStore
The store from createHaStore. The provider connects it while mounted and disconnects it after the last provider using it unmounts; it never closes the transport.
childrenReactNode
Your app.
useHaStore(): HaStore

The store itself, for its actions and background reads. Throws outside an HaProvider.

useHaSelector<T>(selector: (snapshot: HaSnapshot) => T): T

Select one value from the snapshot; the caller re-renders only when it changes (`Object.is`). The selector must return a stable value: a primitive, or an object the store already holds.

useHaEntity(entityId: string): HassEntity | undefined

One raw entity. Unchanged entities keep their identity across updates, so other entities' changes don't re-render.

useHaEntities(entityIds: readonly string[]): ReadonlyArray<HassEntity | undefined>

Several raw entities, in the order asked. The list keeps its identity until one of these entities changes, so a tile composed of many entities re-renders for its own, never for the rest of the house.

useHaStatus(): HaConnectionStatus

The connection’s state: idle, connecting, connected, disconnected or error. See Connection status.

useHaGeneration(): number

A number that moves on every connect, disconnect, loss and recovery. A read or action that settles under another generation than it began under has an unknown outcome.

useHaSnapshot(): HaSnapshot

The whole snapshot; re-renders on every entity change in the house. Prefer a narrower hook.

From @dashhaus/core. Areas, history and requests has examples.

useHaRegistry(): HaRegistry | undefined

The Home Assistant registries, typed, read once per connection generation and shared by every caller. Resolves to `undefined` while connecting, after a failure, and when the transport lacks generic commands. It depends on registry data only, so an entity changing state never re-reads or recomputes it.

useHaAreas(): readonly HaAreaEntities[] | undefined

Every area with the entity ids that belong to it, from the same cached registry read. The entity ids keep the entity registry's order, and an entity that inherits its device's area is grouped there. Areas with no entities are still returned, with an empty list.

useHaArea(entityId: string): string | undefined

Resolve an entity's Home Assistant area name. The registries are shared with every tile; any failure (including a transport without `sendMessage`) resolves to undefined and never touches action state.

useHaHistory(entityIds: readonly string[], load?: boolean, now?: Date): HaHistoryRecord[] | undefined

Today's history for one or more entities: the recorded day from Home Assistant (`history/history_during_period`), extended by every live change seen since that read. The read is shared by callers asking for the same entities, and repeated for every new connection generation and local day, so the strip rolls over at midnight. Any failure (including a transport without `sendMessage`) collapses to `undefined`. Pass the tile's clock as `now` so the day follows it.

useHaRequest<T>(key: string | undefined, fetch: (store: HaStore) => Promise<T>, options?: HaRequestOptions): HaRequestState<T>

One background read through the shared store, deduplicated across callers by store, connection generation and `key`. It runs while connected, again for every new generation, and whenever `key` changes; pass `undefined` to skip it. Results from an older generation are dropped, so a value never outlives the connection that produced it.

From @dashhaus/core/hooks, for any page.

useNow(fixed?: Date): Date

The current time, updated on each minute boundary (not every 60s from mount, which drifts). Background tabs throttle timers and a wall tablet may sleep, so when the page becomes visible again the clock catches up at once. Pass `fixed` to freeze it (stories, screenshots, tests).

useDelayLeft(delay: number | undefined, since: number | undefined, active: boolean): number | undefined

Seconds left in an alarm's exit or entry delay, ticking each second: `delay` is the delay's length in seconds, `since` when it started (ms since the epoch). A tab woken from the background catches up at once. Undefined while not `active`, or with no way to know (no length, or no start).

useScrollSpy<T extends HTMLElement>(rootRef: RefObject<T | null>, key?: unknown): readonly [string | null, (id: string) => void]

Which section of a page is being read, for a nav that marks it, and a way to jump to one. Sections are the elements under `rootRef` with a `data-section-id`; the one in view is the last whose top has passed a line a third of the way down the viewport, the first while the page hasn't scrolled past its top, or the last section once the page is scrolled to its end. It listens for scrolls on the whole document (in the capture phase), so it follows the page whichever ancestor scrolls it: the window in Storybook, Home Assistant's view inside the frontend, through shadow roots. `key` re-reads the sections when the page's content changes.

useHashPage(pages: readonly string[], main: string): readonly [string, (id: string) => void]

The page a dashboard shows, kept in the URL hash (`#page=living`; the main page has none), so the phone's back button returns to the previous page and a link can open one. Going to a page pushes a history entry and keeps the path and whatever state the host router keeps there; back and forward are read from the hash again.

useDemoClimate is there too: the local climate state the skins’ climate stories share, for stories only.

From @dashhaus/core/primitives: behaviour and accessibility for controls you draw yourself. Interaction primitives shows each in use.

usePress(options: PressOptions): { type, aria-pressed, aria-disabled, disabled, data-holding, onPointerDown, onPointerMove, onPointerUp, … }

A button's behaviour, for any skin to draw: tap to press, optionally hold, optionally latch. Spread the result onto a `<button>`. A hold fires after 450 ms unless the pointer moves more than 10 px, buzzes where the device can, sets `data-holding` while it builds, and swallows the click that ends it. The context menu a long touch would open is suppressed while a hold is wired.

useSlider<T extends HTMLElement>(options: SliderOptions<T>): { value, fraction, rootProps, hitProps, inputProps }

A one-value slider's behaviour, for any skin to draw. Spread `rootProps` on the track's box (it gets `data-anim` while a jump animates), `hitProps` on a layer over it that takes the pointer (tap anywhere, or drag; it leaves vertical swipes to the page), and `inputProps` on a visually hidden `<input>`, which carries the keyboard and screen readers: give it its id and label. `fraction(v)` places a value along the track, 0-1.

useRangeSlider<T extends HTMLElement>(options: RangeSliderOptions<T>): { low, high, fraction, rootProps, hitProps, lowInputProps, highInputProps }

Two ordered values on one track (a heat/cool range), low <= high always, drawn by any skin. A tap moves the nearer thumb; a drag moves the thumb nearer where it began, and when the thumbs overlap the tap's side or the drag's first direction picks one. Spread `rootProps`, `hitProps` as for `useSlider`, and `lowInputProps` and `highInputProps` on two hidden range inputs.

useSheet(options: SheetOptions): { mounted, dialogProps }

A modal sheet's behaviour, for any skin to draw: a `<dialog>` that mounts its content when opened, shows as a modal, locks the page's scroll, and on close sets `data-closing` for the skin's exit transition before it closes and unmounts. Escape and a backdrop click close it. On phones the handle drags it down: past 30% of its height (140 px at most) or any flick dismisses it, a short slow drag snaps back, and dragging up meets growing friction. One pointer at a time. Spread `dialogProps` on a `<dialog>` and render the content only while `mounted`; `[data-autofocus]` inside takes focus on open. It stays a native modal `<dialog>`, never a portalled one: the platform puts it in the top layer and makes the page behind inert, and that holds inside the Lovelace card's shadow root, where a portalled dialog's document-level focus trap does not.

useKeypad(options: KeypadOptions): { code, error, busy, press, back, type, submit, inputProps, … }

A code keypad's behaviour, for any skin to draw: the code typed so far, a digit key's press, delete, enter, and the answer (busy, then a wrong code or a failed call). A physical keyboard types too: digits (and letters for a text code), Backspace, and Enter, which submits unless it lands on a button outside the keypad with nothing typed. Spread `rootProps` onto the keypad's root: it takes focus when it appears inside an open dialog, and a dialog that is opening focuses it through `data-autofocus`. A text code can't be typed on the digit keys, and a touch screen has no physical keyboard, so a text keypad also draws a real input: spread `inputProps` onto an `<input>`. It takes the focus in the root's place, so a phone or a wall panel brings up its own keyboard, and Enter in it submits. The digit keys still type into the same code.

Each entity hook is three pure functions you can call without React or a store, for a host that keeps Home Assistant’s state its own way: a mapper from raw state to the model, history parsing, and an action factory over anything with a callService. For a light:

lightFromHass(raw: HassEntity | undefined, entityId: string, options?: LightMapOptions): LightEntity

Map one raw Home Assistant light state to the existing presentational model.

lightHistoryFromHass(records: Records, entityId: string, now?: Date): LightHistory

Replicates the hourly intensity + onMinutes computation of `useDemoLight`.

createLightHandlers(caller: LightServiceCaller, entity: LightEntity, options?: LightHandlerOptions): LightHandlers

Create safe callback handlers for the existing LightTile contract.

Every kind has the same three: climateFromHass and createClimateHandlers, coverFromHass and createCoverHandlers, and so on; a reading has no handlers.