Haunted: Hooks for Web Components

Avatar of Chris Coyier
Chris Coyier on

I was just chatting with Dave and he told me about Haunted. It’s hooks, but for native web components! Pretty cool. I think the existence of stuff like this makes using web components more and more palatable — particularly in that totally-native no-build-step-needed-at-all kinda way.

I get that there are all sorts of issues with web components, but the things that typically turn me away from them are a lack of nice templating and rerendering and no state management.

But we can knock those two out right quick these days…

First, making a component like <my-app> is perfectly comfortable:

import { html } from "https://unpkg.com/lit-html/lit-html.js";
import { component } from "https://unpkg.com/haunted/haunted.js";

function App() {
  return html`
    <div class="module">
      Hello, World!
    </div>
  `;
}

customElements.define("my-app", component(App));

Then we could add some state with hooks:

import { html } from "https://unpkg.com/lit-html/lit-html.js";
import { component, useState} from "https://unpkg.com/haunted/haunted.js";

function App() {
  const [name, setName] = useState("Chris");
  return html`
    <div class="module">
      Hello, ${name}!
    </div>
  `;
}

customElements.define("my-app", component(App));

The CodePen Challenge this week is using the Star Wars API, so let’s make a fetch request and use that to fill state. That’s a great use case for useEffect.

import { html } from "https://unpkg.com/lit-html/lit-html.js";
import { component, useState, useEffect } from "https://unpkg.com/haunted/haunted.js";

function App() {
  
  const [planets, setPlanets] = useState([]);
  useEffect(() => {
    fetch('https://swapi.co/api/planets/?page=2')
      .then(response => {
        return response.json();
      })
      .then(data => {
        let planets = data.results;
        // remove ones with no diameters
        planets = planets.filter(planet => planet.diameter !== "0");
        setPlanets(planets);
      });
  }, []);

  return html`
    <style>
      /* Shadow DOM styles */
    </style>
    <div class="all-planets">
      ${planets.map(planet => html`
        <div class="planet" style="--dia: ${planet.diameter}px">
           <span class="planet-name">
              ${planet.name}
           </span>
        </div>
      `)}
    </div>
  `;
}

customElements.define("my-app", component(App));

That’s a proper little web component!

See the Pen
Star Wars API with Haunted.js
by Chris Coyier (@chriscoyier)
on CodePen.