A collection of Lit.js reactive controllers for managing side effects, effect groups, and memoization in your Lit-based applications.
You can install the library via npm :
npm i @lookwe/lit-controllers
The EffectController
runs a callback function whenever the specified dependencies change. You can control whether the effect runs before or after the render with the strategy
option.
import { html, LitElement } from 'lit';
import { state } from 'lit/decorator.js';
import { EffectController } from '@lookwe/lit-controllers';
class MyElement extends LitElement {
@state() accessor #count = 0;
// EffectController: Executes the effect when `count` changes
#_effectController = new EffectController(
this,
([count], [previousCount]) => {
console.log('Effect triggered!');
console.log('Current count:', count);
console.log('Previous count:', previousCount);
},
() => [this.#count], // Dependencies (the `count` property)
{ strategy: 'updated' }, // Effect will run after the render
);
render() {
return html`
<button @click="${this.#increment}">Increment</button>
<p>Count: ${this.#count}</p>
`;
}
#increment() {
this.#count += 1;
}
}
customElements.define('my-element', MyElement);
Parameters:
callback
: The function called with the current and previous dependencies.deps
: A function that returns an array of dependencies.options
: Optional. Set the strategy to "update" (before the render) or "updated" (after the render).
The EffectGroupController
manages multiple EffectController
instances, grouping them together based on shared dependencies. You can add or remove all controllers at once from the host element.
import { html, LitElement } from 'lit';
import { state } from 'lit/decorator.js';
import { effect, EffectGroupController } from '@lookwe/lit-controllers';
class MyElement extends LitElement {
@state() accessor #count = 0;
@state() accessor #name = 'John';
// EffectGroupController: Executes multiple effects together
#_effects = new EffectGroupController(
this,
effect(
([count]) => {
console.log('Effect 1 triggered!');
console.log('Current count:', count);
},
() => [this.#count],
),
effect(
([name]) => {
console.log('Effect 2 triggered!');
console.log('Current name:', name);
},
() => [this.#name],
),
);
render() {
return html`
<button @click="${this.#increment}">Increment</button>
<button @click="${this.#changeName}">Change Name</button>
<p>Count: ${this.#count}</p>
<p>Name: ${this.#name}</p>
`;
}
#increment() {
this.#count += 1;
}
#changeName() {
this.#name = this.#name === 'John' ? 'Jane' : 'John';
}
}
customElements.define('my-element', MyElement);
Methods :
removeControllers()
: Removes all theEffectController
instances from the host.addControllers()
: Adds all theEffectController
instances back to the host.
The MemoController
memoizes the result of a callback function based on its dependencies. It prevents unnecessary re-evaluations when dependencies haven't changed.
import { html, LitElement } from 'lit';
import { state } from 'lit/decorator.js';
import { MemoController } from '@lookwe/lit-controllers';
class MyElement extends LitElement {
@state() accessor #count = 0;
// MemoController: Memoizes the result of the callback
#memoController = new MemoController(
this,
([count]) => {
console.log('Memoized value calculated!');
return count * 2;
},
() => [this.count],
);
render() {
return html`
<button @click="${this.#increment}">Increment</button>
<p>Memoized value: ${this.#memoController.value}</p>
`;
}
#increment() {
this.#count += 1;
}
}
customElements.define('my-element', MyElement);
Parameters:
callback
: A function that computes the value based on the dependencies.deps
: A function that returns the dependencies array.
Methods:
value
: Returns the memoized value.getValue(forceCheckDeps = false)
: Returns the memoized value, optionally forcing a re-evaluation of the dependencies.