-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Which package(s) are affected?
Other/unknown (please mention in description)
Description
Using @lit-labs/signals, using watch on one signal sometimes prevents render from being called when an unrelated signal changes. It doesn't appear to happen with all other signals, but I have a case that replicates it. Here's the component:
import { LitElement, css, html } from "lit";
import { customElement } from "lit/decorators.js";
import { signal, SignalWatcher, watch } from "@lit-labs/signals";
@customElement("lit-list")
export class LitListElement extends SignalWatcher(LitElement) {
#items = signal<string[]>([]);
#item = signal("");
render() {
const items = this.#items.get();
console.log(`render: ${items.length}`);
return html`<div>
<div>
<label
>Item:
<input
type="text"
.value=${watch(this.#item)}
@input=${this.#itemInput}
/></label>
<button @click=${this.#onAddItemClick}>Add</button>
</div>
<ul>
${items.map((item) => html`<li>${item}</li>`)}
</ul>
</div> `;
}
#itemInput(e: InputEvent) {
const value = (e.target as HTMLInputElement).value;
this.#item.set(value);
}
#onAddItemClick(e: MouseEvent) {
e.preventDefault();
const item = this.#item.get();
const items = this.#items.get();
const newItems = [...items, item];
console.log(
`Adding "${item}" to [${items.map((item) => `"${item}"`)}] (${
items.length
}) => [${newItems.map((item) => `"${item}"`)}] (${newItems.length})`
);
this.#items.set(newItems);
}
static styles = css`
:host {
display: block;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"lit-list": LitListElement;
}
}this.#item is a signal around a string for the current item being typed, and this.#items is a signal around an array for the list of items. With the code above, typing an item and clicking Add does add the item to this.#items, but doesn't cause a re-render. As I understand it, since we're reading the value of the this.#items signal within render, changing it should cause a re-render.
If you change the .value on the input, though, from .value=${watch(this.#item)} to .value=${this.#item.get()} (e.g., don't use watch), the problem goes away and the component re-renders when this.#items changes.
(Note that I'm setting an entirely new array when updating this.#items, not just pushing to it, so it's not the classic issue of it being the same array. [The same problem happens if I use SignalArray from signal-utils, but I wanted a minimal example.])
Reproduction
The code above replicates the issue. Here's a live example: https://stackblitz.com/edit/vitejs-vite-heivabvr?file=src%2Flit-list-element.ts
Workaround
The workaround is to not use watch.
Is this a regression?
No or unsure. This never worked, or I haven't tried before.
Affected versions
Lit v3.2.1, @lit-labs/signals v0.1.1
Browser/OS/Node environment
Browser: Chromium 131.0.6778.85 (Official Build) for Linux Mint (64-bit)
OS: Linux Mint 21
Node: v23.3.0
npm: v10.9.0
Metadata
Metadata
Assignees
Labels
Type
Projects
Status