Skip to content

Humidifier

One humidifier.* entity, a dehumidifier included: on and off, a target humidity and its modes.

import { useHaHumidifier } from "@dashhaus/core";
/** The room's humidity, the target, and a slider for it when the device takes one. */
export function Humidity({ entityId }: { entityId: string }) {
const { entity, actions } = useHaHumidifier(entityId);
const { onTargetChange } = actions;
return (
<fieldset disabled={entity.state === "unavailable"}>
<legend>
{entity.name} ({entity.kind ?? "humidifier"}): {entity.currentHumidity ?? "—"}% now
</legend>
<button type="button" aria-pressed={entity.state === "on"} onClick={actions.onToggle}>
{entity.state === "on" ? "On" : "Off"}
</button>
{onTargetChange && (
<label>
Target {entity.targetHumidity}%
<input
type="range"
min={entity.minHumidity}
max={entity.maxHumidity}
value={entity.targetHumidity ?? entity.minHumidity}
onChange={(event) => onTargetChange(event.currentTarget.valueAsNumber)}
/>
</label>
)}
</fieldset>
);
}

kind is dehumidifier when Home Assistant’s device_class says so: it dries to its target instead of adding moisture.

<HaHumidifier> is useHaHumidifier as a component that draws nothing of its own. It takes every option of the hook as a prop, and hands the hook’s result to its child function. Use it where hooks can’t go, such as one per item of a list.

entityIdstring
The Home Assistant entity id.
children(live: HaHumidifierResult) => ReactNode
Draws the hook's result. The component itself draws nothing.
useHaHumidifier(entityId: string, options?: HaHumidifierOptions): HaHumidifierResult

One live humidifier for any UI: the mapped entity, today's history and its service actions.

It reads the device, its area and today’s history.

name?string
Overrides Home Assistant's friendly name.
area?string
Overrides the area Home Assistant's registries resolve for the entity.
lastSeen?string
Overrides when the entity was last reachable, shown while it is unavailable.
history?HumidifierHistory
Host-owned history override; when omitted, today's history is read from Home Assistant.
loadHistory?boolean
Set false to skip the Home Assistant history read.
onActionError?(error: HaActionError) => void
Called with an HaActionError when an action is refused or fails.
entityHumidifierEntity
historyHumidifierHistory
actionsHumidifierHandlers
entityIdstring
The Home Assistant entity id.
namestring
Its name: the `name` option, else Home Assistant's friendly name, else its entity id.
area?string
Where it is: "Bedroom".
state"unavailable" | "off" | "on"
kind?"humidifier" | "dehumidifier"
HA `device_class`: a dehumidifier dries to its target. Default humidifier.
action?HumidifierAction
Absent when the device doesn't report it.
currentHumidity?number
Relative humidity in the room, 0-100.
targetHumidity?number
Target relative humidity, 0-100. Absent when the device has no setpoint.
minHumiditynumber
maxHumiditynumber
mode?string
modes?string[]
HA `available_modes`: "normal", "eco", "sleep", "boost"…
lastChanged?string
lastSeen?string
While it is unavailable, when it was last reachable: the `lastSeen` option, else what Home Assistant reports.

Today, from Home Assistant’s recorder, kept up to date by every change since. Pass history to supply your own, or loadHistory={false} to skip the read.

samplesHourSample[]
1 for hours it was on, 0 off, "unavailable", null for hours ahead. Fractions for part of an hour.
currentHournumber
onMinutesnumber
Minutes on today, including the current hour.
onToggle() => void
Calls `humidifier.toggle`.
onTargetChange?(humidity: number) => void
Only for a device that reports a target. Calls `humidifier.set_humidity`, rounded to a whole percent; outside `minHumidity` to `maxHumidity` reports `invalid_input`.
onModeChange?(mode: string) => void
Only with modes. Calls `humidifier.set_mode`; a mode the device doesn't list reports `unsupported`.

Every action on an unavailable entity is reported to onActionError as unsupported and never sent.

Hooks can’t be called in a loop, so a list renders one headless component per entity. The selector returns a string: useHaSelector re-renders only when its value changes, and a string of ids changes only when an entity comes or goes. Lists skip each item’s history read with loadHistory={false}.

import { HaHumidifier, useHaSelector } from "@dashhaus/core";
import type { HaSnapshot } from "@dashhaus/core";
const humidifierIds = (snapshot: HaSnapshot) =>
Object.keys(snapshot.entities).filter((id) => id.startsWith("humidifier.")).sort().join(" ");
export function AllHumidifiers() {
const ids = useHaSelector(humidifierIds).split(" ").filter(Boolean);
return (
<ul>
{ids.map((id) => (
<li key={id}>
<HaHumidifier entityId={id} loadHistory={false}>
{({ entity }) => (
<>
{entity.name}: {entity.action ?? entity.state}, {entity.currentHumidity ?? "—"}%
</>
)}
</HaHumidifier>
</li>
))}
</ul>
);
}
import { useHaHumidifier } from "@dashhaus/core";
/** The device's own modes (normal, eco, sleep, boost…), one button each. */
export function Modes({ entityId }: { entityId: string }) {
const { entity, actions } = useHaHumidifier(entityId, { loadHistory: false });
const { onModeChange } = actions;
if (!onModeChange || !entity.modes) return null;
return (
<div role="group" aria-label={`${entity.name} mode`}>
{entity.modes.map((mode) => (
<button key={mode} type="button" aria-pressed={entity.mode === mode} onClick={() => onModeChange(mode)}>
{mode}
</button>
))}
</div>
);
}

modes is Home Assistant’s available_modes: normal, eco, sleep, boost, whatever the device has.

Orb’s HaHumidifierTile and Field’s HaHumidifierModule are useHaHumidifier above each skin’s card. They take the hook’s options as props, plus their own:

import { HaHumidifierTile } from "@dashhaus/orb";
import { HaHumidifierModule } from "@dashhaus/field";
export function OrbHumidifier() {
return <HaHumidifierTile entityId="humidifier.nursery" />;
}
export function FieldHumidifier() {
return <HaHumidifierModule entityId="humidifier.nursery" footprint="compact" />;
}
entityIdstring
The Home Assistant entity id.
showControl?boolean
Compact only. false removes the inline control, shows the value in its place, and makes the row open the details. Default true.
footprint?CardFootprint
Card footprint. The skin's stylesheet sets the heights; the three names are the shared vocabulary.
defaultOpen?boolean
defaultAction?undefined

Every state of both is in Storybook: Orb and Field.