Integrating Preact Signals with Lit via Custom Directive
Tags: preact, signals, lit, javascript
Published: 2023-11-13 20:44:02

## 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`](https://preactjs.com/guide/v10/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

```typescript
class SignalDirective extends Directive {
  #signal: Signal | undefined;
  ...
}
```

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

#### Render Method

```typescript
render(signal) {
  if (this.#signal !== signal) {
    this.#signal = signal;
    effect(() => this.render(signal));
  }
  return html`${signal.value}`;
}
```

- **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

```typescript
export const litSignal = directive(SignalDirective);
```

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

## Usage Example

### Signal Initialisation

```typescript
const tagsForCurrentNoteSignal = signal<Tag[]>([]);
```

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

### Computed Value

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

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

### Template Utilization

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

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.