Skip to content

Use the hooks in your app

Put Dashhaus in a React app of yours: install the packages, connect the store, then draw one light twice. The first uses your own markup on the headless hook; the second, a skin’s ready-made component. @dashhaus/core on its own is enough for the first: no skin, no generator and no Lovelace card come with it. If you only want a finished dashboard in Home Assistant, Install from the Marketplace needs no React at all.

Terminal window
npm install @dashhaus/core react react-dom home-assistant-js-websocket

@dashhaus/core has no markup and no CSS. React 18.3 or newer (19 included) is a peer. home-assistant-js-websocket is only needed by @dashhaus/core/websocket, which opens the connection.

Everything reads Home Assistant through one store. createHaStore turns a transport into the store and HaProvider connects it while it is mounted. Your app owns the login and the transport: here Home Assistant’s OAuth flow from home-assistant-js-websocket, then connectHomeAssistant from @dashhaus/core/websocket.

main.tsx
import { createRoot } from "react-dom/client";
import { getAuth } from "home-assistant-js-websocket";
import { HaProvider, createHaStore } from "@dashhaus/core";
import { connectHomeAssistant } from "@dashhaus/core/websocket";
import { App } from "./App";
// Your app owns the login: here, Home Assistant's own OAuth flow through home-assistant-js-websocket.
const auth = await getAuth({ hassUrl: "http://homeassistant.local:8123" });
const transport = await connectHomeAssistant(auth);
const store = createHaStore(transport);
createRoot(document.getElementById("root")!).render(
<HaProvider store={store}>
<App />
</HaProvider>,
);
App.tsx
import { LivingRoomLight } from "./LivingRoomLight";
export function App() {
return (
<main>
<h1>Home</h1>
<LivingRoomLight />
</main>
);
}

Inside Home Assistant, a skin’s Lovelace card does this for you on the frontend’s own connection; you never handle a token there. Connecting and auth covers both, and a kiosk.

useHaLight hands over the light as a plain LightEntity, today’s history, and its actions. You draw it:

LivingRoomLight.tsx
import { useHaLight } from "@dashhaus/core";
/** One light, drawn your way: the hook hands over the light and what it can do. */
export function LivingRoomLight() {
const { entity, actions } = useHaLight("light.living_room");
const on = entity.state === "on";
return (
<button type="button" aria-pressed={on} disabled={entity.state === "unavailable"} onClick={actions.onToggle}>
{entity.name}: {on ? `on${entity.brightness == null ? "" : `, ${entity.brightness}%`}` : "off"}
</button>
);
}

A tap calls light.toggle. When Home Assistant reports the new state, the store updates and the button follows: the hook never assumes the call worked.

Every kind of entity has a hook, a headless component and a tile and a module, so the same swap works for any of them. Entities lists them all.

  1. Say when the dashboard can’t reach Home Assistant: Connection status.
  2. Draw a list of entities, every light in the house, with the headless components: Light.
  3. Test a connected control without Home Assistant: Testing and demo data.