Skip to content

[RRFC] Stateful/reactive render functions #32

Open
@sorvell

Description

@sorvell
  • I searched for an existing RRFC which might be relevant to my RRFC

Motivation

Functions are the simplest unit of composition and the first tool a developer leverages to accomplish almost any task. lit empowers developers to write shareable interoperable code that creates rich interactive interfaces. Minimizing the distance and friction between starting with a function and ending with a great UI serves that goal.

There are 2 main pieces of this puzzle: (1) describing rendering, (2) managing reactive state.

Describing rendering: Lit's html and css TTL syntaxes are excellent at this, and can easily be returned from a function.

Managing reactive state:

  • Lit's reactive properties provide an excellent mechanism for managing reactive state and make sense to use when creating custom elements that extend LitElement. If a dev started with a function, they must move to a class, and this is fine when it makes sense.
  • However, the new @lit-labs/preact-signals library provides an alternative for simpler use cases and it more seamlessly builds on top of functions.
    • Signals are reactive state containers. Their reactivity does require using them within a scope that records dependencies (aka effects), but the lit signals package provides SignalWatcher and watch to deal with this.
    • The one remaining problem is having a convenient way to create signals within a function. They can certainly be passed in as arguments, but what if they are internal the ui being managed by the function?

This last issue is addressed below...

Example

Consider creating a simple counter:

In a LitElement, it looks like:

@state()
accessor count = 0;
  
render() {
  return html`<button @click=${() => this.count++}>${this.count}</button>`;
}

With a function using signals, this can be:

const renderCounter = () => html`<button @click=${() => count.value++}>${count}</button>`;

But this doesn't work because we need to get the count signal from somewhere.

How

React solves this problem with hooks, but these have tradeoffs. Lit can make this more straightforward by leveraging the fact that all Lit templates are rendered into persistent "parts," and access to them is provided via the directives API.

So, all we need is a simple directive that can initialize state. Here's the updated counter example:

const renderCounter = (use) => {
  const count = use.state(() => signal(0));
  return html`<button @click=${() => count.value++}>${count}</button>`;
}

Then use it like this:

  return html`counter: ${stateful(renderCounter)}`

See working prototype.

The stateful directive provides a state method which memoizes the result of its argument.

References

  • n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions