Lovelace: React Custom Lovelace Card with `@preact/signals-react`

Hello!

I have been looking for an example where someone mounts a React component to a lovelace web component, but could not find any clean examples. So I made my own.

I have come up with a POC that mounts a React component to a web component’s shadow root.

The other issue is the repeated rendering when hass or config is changed. This can be solved very easily now with @preact/signals-react.

Pros:

  • React based Lovelace Card
  • Build the component with modern TS/TSX/JS/JSX
  • No need for HASS connectors - hass consumed directly from Home Assistant
  • Highly configurable
  • Use any other npm libraries

Cons:

  • Requires build step.
  • No direct pipelines (yet).
  • Can’t use Radix UI (because they use document.documentElement to deal with dark mode :sob:).

I’d like to hear comments and thoughts about this!

1 Like

Super interested to see where this goes. Opens up a lot for folks with React skills who dont want to learn crazy yaml hacks

Wow, such great work! Thank you, @samuelthng !

Your README is simple, quick, and it works well.

I would like to know if you’ve found a way to live preview your card during development, similar to how we develop a classic React app. Perhaps simulate a config file outside of the Home Assistant instance? Also, if we update the .js file located in /local/www, how can we easily update the card?

I have to manually clear the browser’s cache.

I hope my questions are clear enough.

I haven’t been working on this, got put on a major project this year so all my personal projects have been dragged down lately :sob:

That aside, I haven’t found a fix for the stale js file, but what HACS does is include a cache breaker hash for each imported resource. If you’re keen to make some progress, I’m guessing that will be the direction eventually.

As for having a local version of the script, you could mock the value of the hass and config signal and replace it wherever required. Since it’s a signal, it’s gonna work no matter what updates it.

You can find the signals in utilities/registerCard.ts, might have to export the signals individually.

Edit: @Nycco sorry not sure why the post wasn’t replied to your comment.

Thanks for your answer @samuelthng

I managed to get past the cache problem using the browser’s inspector mode and a hard reload.

Regarding the local version, it’s quite easy to export the hass and config signal, but I don’t know how I can preview the project locally with the ‘npm run dev’ command, for example.

Do you have any suggestions?

You’d need to mount the app’s custom element on a page.

Currently it’s being registered as a custom element in registerCard.ts so you could make use of that and register the app as a custom component, then just consume it in the body of a html file.

Will need some tweaking to make it happen.

TL;DR;

  • createReactCard.tsx - Creates a custom element.
  • registerCard.ts - Sets up signals and registers the custom element (see web component).

Load the bundle in a page using a script tag, then place <react-card> somewhere in your html file.

Thanks @samuelthng
I will give a try.

Last question :wink: do you think there’s a way to get the Material Design icon of HA into the custom card? To use the icon property in the YAML custom card config.

Home Assistant loads icons on the dashboard using the <ha-icon icon="mdi:something" class="icon" > custom element. You won’t be able to load the icons when testing locally, but you can definitely use them in the app and have them load on the dashboard. (I think, not sure if there are any caveats)

1 Like

Love this! Thanks for building this out and documenting the process.

Don’t feel obligated to look into this if you aren’t sure because this is still definitely way better than the vanilla Javascript code I was writing for custom cards before. But, I use React Redux at work which to my understanding is nice for pulling state up globally so you don’t have to prop drill any of the state.

Anyway, I tried providing a store to the app in the createReactCard() utility but I don’t seem to actually have access to it when I run on HA. Curious if you know of any reason that couldn’t work on HA? I have it working in my hacky little local dev setup that bypasses the registerCard() calls entirely though.

If you (or anyone else discovering this) want to take a look, I made a public repo where you can look into the nonsense I’m trying. I’ll try to remember to update this comment if I get it working so I don’t send anyone on a wild goose chase.

UPDATE: I got it working sorta. I think my reducer setup is a little weird. I’ll try to commit something for other people’s reference when I have it cleaner.

1 Like

Just checked out your repo, I would advise to inject your redux store into App.tsx rather than main so that it works both locally and when registered as a card on HA.

App.tsx is intended to be the entry point for your application, anything else above should be considered boilerplate.

1 Like

Just found out about this today:

Haven’t tried it myself, but it should potentially simplify whatever jank I’ve come up with here.

I found this because it looked promising for integrating tldraw into Home Assistant, but it isn’t as easy as I’d hoped. Could the issues be related to the fact that tldraw uses Radix UI under the hood?

It could be, from what I remember (very vaguely), I had issues injecting styles from Radix. Functionality should not be affected if that’s the cause, but it would look pretty jarring.

React dev here! I have seen different ideas on how to use React to develop custom dashboards but I haven’t been able to get it to work.

In a high level, looks like we need to build the React project, copy that into the HA’s www folder and somehow mount it in a custom element.

I started looking the custom panel approach and recently found this thread which is using it as a Lovelace card.

I would prefer the custom panel approach since I would have more flexibility when it comes to custom layouts.

Anyone actively working on any React approach or have time to pair and help me get a custom panel built in React?