DEV Community

Cover image for Integrating Preact Signals with Lit via Custom Directive
Andy Jessop
Andy Jessop

Posted on

Integrating Preact Signals with Lit via Custom Directive

Introduction

Lit already has a way to use Preact signals in LitElement, the custom element class, but if you're using the standalone Lit (lit-html), you might want similar functionality. This post describes a small directive that provides this for you.

I'm hoping to show you how easy it is to create reactive UIs using these two tiny, but exceptionally powerful, libraries.

Understanding the Basics

@preact/signals

@preact-signals is a library by the Preact team that offers "a reactive primitive for managing application state". Signals are reactive state primitives that automatically update components when their values change.

lit-html

Lit is a simple library for building fast, lightweight web components. It uses lit-html for templating, allowing developers to define and render HTML templates efficiently. These templates can also be used "standalone" - outside of a web component - which is how I'm using them here.

Directive Concept

A directive in Lit is a function that customises the behaviour of a template expression. It offers a way to encapsulate and reuse complex rendering logic within templates.

SignalDirective: Bridging Preact and Lit

The SignalDirective class serves as a bridge between Preact Signals and Lit. It allows Lit templates to reactively update based on changes in Preact Signal values.

Code Analysis

Class Definition

class SignalDirective extends Directive {
  #signal: Signal | undefined;
  ...
}
Enter fullscreen mode Exit fullscreen mode

The SignalDirective extends Directive from Lit, and it maintains a private signal instance.

Render Method

render(signal) {
  if (this.#signal !== signal) {
    this.#signal = signal;
    effect(() => this.render(signal));
  }
  return html`${signal.value}`;
}
Enter fullscreen mode Exit fullscreen mode
  • Signal Binding: The method binds a Signal object to the directive.
  • Effect Hook: Utilizes Preact's effect to trigger re-rendering upon signal change.

Here, we're first checking if we have already cached this signal. If not, we subscribe to its changes by using the @preact/signals effect hook

Exporting the Directive

export const litSignal = directive(SignalDirective);
Enter fullscreen mode Exit fullscreen mode

The litSignal function is exported, making it available for use in Lit templates.

Usage Example

Signal Initialisation

const tagsForCurrentNoteSignal = signal<Tag[]>([]);
Enter fullscreen mode Exit fullscreen mode

A signal is created with an initial empty array of tags.

Computed Value

const tagNames = computed(() =>
  tagsForCurrentNoteSignal.value.map((tag) => html`${tag.name}`),
);
Enter fullscreen mode Exit fullscreen mode

A computed value is created that maps each tag to a Lit template result.

Template Utilization

export function template(): TemplateResult {
  return html`<div>${litSignal(tagNames)}</div>`;
}
Enter fullscreen mode Exit fullscreen mode

The litSignal directive is used within a Lit template, allowing the div element to reactively display the transformed tag names.

Conclusion

The integration of Preact Signals with Lit through a custom directive presents an extremely simple way of defining reactive UIs. I hope that you found this useful.

Top comments (0)