Action errors
An action that doesn’t happen is never silent. Whether the hook refused it before sending (the entity is unavailable,
the value is out of range) or Home Assistant refused it, it becomes an HaActionError, and the hook’s onActionError
option hears of it.
code |
category |
retryable |
Means |
|---|---|---|---|
not_connected |
connection |
yes | The store wasn’t connected, so the call never reached Home Assistant. |
service_failed |
rejected |
yes | Home Assistant refused the call. |
transport_unknown |
connection |
no | The connection changed during the call, so it may or may not have happened. A repeat could do it twice. |
unsupported |
unsupported |
no | The entity, or the transport, can’t do it: an unavailable light, a cover without a stop. |
invalid_input |
invalid_input |
no | The value was wrong before it was sent: a temperature out of range, a brightness that isn’t a number. |
retryable means a repeat is safe on the current connection, not that it will work.
codeHaActionErrorCode- One of the five codes above. Choose your words by this.
categoryHaActionErrorCategory- The code's family.
retryableboolean- Whether repeating the same call is safe on this connection.
domain?string- The service's domain, when known.
service?string- The service, when known.
entityId?string- The entity the action was for, when known.
translationKey?string- Sanitized provider translation key, surfaced only as a string scalar.
providerCode?string- Sanitized provider code, surfaced only as a string scalar.
An error never carries Home Assistant’s own message or payload, which could echo an alarm code. Use code, not
message: the message’s wording can change.
One control
Section titled “One control”import { useState } from "react";import { useHaLight } from "@dashhaus/core";import type { HaActionError } from "@dashhaus/core";
/** Words for a refused or failed action, chosen by its code, never by its message. */function explain(error: HaActionError): string { switch (error.code) { case "not_connected": return "Not connected to Home Assistant. Try again in a moment."; case "service_failed": return "Home Assistant refused that."; case "transport_unknown": return "The connection dropped mid-way, so it may or may not have happened."; case "unsupported": return "This light can't do that."; case "invalid_input": return "That value is out of range."; }}
export function LampWithErrors({ entityId }: { entityId: string }) { const [problem, setProblem] = useState<string>(); const { entity, actions } = useHaLight(entityId, { onActionError: (error) => setProblem(explain(error)) }); return ( <div> <button type="button" aria-pressed={entity.state === "on"} onClick={actions.onToggle}> {entity.name} </button> {problem && <p role="alert">{problem}</p>} </div> );}The whole dashboard
Section titled “The whole dashboard”The store keeps the latest failed action as lastActionError in its snapshot, whichever control made it. It keeps
the call to offer again only when a repeat is safe, and never keeps a call that carries an alarm code.
import { useHaSelector, useHaStore } from "@dashhaus/core";
/** * One bar for the whole dashboard: the store keeps the latest failed action, and offers it again only when a repeat * is safe on this connection (an alarm code is never kept). */export function RetryBar() { const store = useHaStore(); const error = useHaSelector((snapshot) => snapshot.lastActionError); if (!error) return null; return ( <div role="alert"> {error.domain ? `${error.domain}.${error.service ?? "call"} failed.` : "An action failed."} {error.retryable && store.canRetryLastAction?.() && ( <button type="button" onClick={() => void store.retryLastAction?.().catch(() => undefined)}> Try again </button> )} <button type="button" onClick={() => store.clearActionError?.()}> Dismiss </button> </div> );}A successful call means Home Assistant accepted the command, not that the device did it. The entity’s next state is the answer: a light that didn’t turn on still shows off.
Alarm panels
Section titled “Alarm panels”onArm and onDisarm answer instead of only reporting: they resolve true when Home Assistant took the call and
false for a wrong code, which isn’t an error. Any other failure goes to onActionError and rejects the promise.
Alarm shows all three.
A failed background read, such as a forecast or the registries, is never an action error. A forecast failure goes to
the weather hook’s onForecastError; useHaRequest returns it as error; the history and registry hooks read as
undefined.