Skip to content

Raw state and selectors

The entity hooks cover the kinds Dashhaus models. For anything else, read the store directly. The store holds Home Assistant’s raw state, every entity as { entity_id, state, attributes, last_changed, … }, and keeps an unchanged entity’s object the same across updates. The hooks below lean on that: each re-renders only when what it read has changed.

useHaEntity returns one entity as Home Assistant sent it, or undefined until it arrives. A media_player has no model here, so read its attributes yourself:

import { useHaEntity } from "@dashhaus/core";
/** Any entity, raw: Home Assistant's state and attributes as they arrive, for a domain with no model yet. */
export function MediaTitle() {
const player = useHaEntity("media_player.living_room");
if (!player || player.state === "unavailable") return null;
const title = player.attributes.media_title;
return <p>Now playing: {typeof title === "string" ? title : player.state}</p>;
}

Attributes are unknown: check their type before you use them. useHaEntities does the same for a list of ids, and keeps the array the same until one of those entities changes, so a card made of several entities re-renders for its own and not for the rest of the house.

entity_idstring
statestring
attributesHassAttributes
last_changed?string
last_updated?string
last_reported?string
context?unknown

useHaSelector runs a function over the whole snapshot and re-renders only when its result changes (compared with Object.is). Return a number, a string, a boolean, or an object the store already holds; a new array or object every time would re-render on every change in the house.

import { useHaSelector } from "@dashhaus/core";
import type { HaSnapshot } from "@dashhaus/core";
/** A number re-renders only when it changes, so this badge ignores every other change in the house. */
const lightsOn = (snapshot: HaSnapshot) =>
Object.values(snapshot.entities).filter((entity) => entity.entity_id.startsWith("light.") && entity.state === "on").length;
export function LightsOnBadge() {
const count = useHaSelector(lightsOn);
return <span>{count === 1 ? "1 light on" : `${count} lights on`}</span>;
}

The entity pages’ “every entity” examples use the same rule: they select the ids as one string, which only changes when an entity comes or goes.

statusHaConnectionStatus
entitiesHassEntities
error?unknown
lastActionError?HaActionError
generationnumber
Increments whenever the connection is lost, recovers, fails, or the store connects or disconnects.

useHaSnapshot returns all of it and re-renders on every change in the house: for a diagnostics page, not for a dashboard.

useHaStore returns the store. Calling a service through it is what the entity hooks’ actions do:

getSnapshot() => HaSnapshot
subscribe(listener: () => void) => () => void
callService(domain: string, service: string, serviceData?: Record<string, unknown> | undefined, options?: HaServiceCallOptions | undefined) => Promise<unknown>
query(domain: string, service: string, serviceData?: Record<string, unknown> | undefined, options?: HaServiceCallOptions | undefined) => Promise<unknown>
Background read-only request that stays out of action ordering and `lastActionError`.
callApi(type: string, payload?: Record<string, unknown> | undefined) => Promise<unknown>
Generic websocket command (e.g. recorder history reads) that stays out of action ordering and `lastActionError`.

callService is an action: it is ordered with the others, and a failure becomes the store’s lastActionError. query and callApi are background reads that never do either. In a component, a read belongs in useHaRequest, which shares it and drops a stale answer.