State
The state
method creates a reactive state object that automatically triggers UI updates whenever the state changes.
Overview
LemonadeJS implements state management differently from frameworks like React and Lit. It maintains a list of micro-reactive events tied directly to the DOM or component attribute references. These references are updated immediately when the state changes, without needing reconciliation or re-rendering the template. For example:
Documentation
Method | Description |
---|---|
state |
@param {any} value - initial state value @param {Function} Effect - Optional callback function that runs when the state value changes. Receives the new value and the old value as parameters. @return {Function} state state(value: any, effect?: Function) => state{} |
Updating the State
The state implements a getter and setter for the property value, this way you can change the state by simple calling the property value.
let color = state('orange');
// To update the color you can use the property value
color.value = 'red';
Working Example
<html>
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
let { state } = lemonade;
function Test() {
let status = state(true);
const update = () => {
status.value = ! status.value;
}
return render => render`<div>
<p><Switch value="${status}" /></p>
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
lemonade.render(Test, document.getElementById('root'));
</script>
</html>
import { state } from 'lemonadejs';
export default function Test() {
let status = state(true);
const update = () => {
status.value = ! status.value;
}
return render => render`<div>
<Switch value="${status}" /><br/>
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
State Effect
The state method accepts an optional effect callback that runs when the state value changes. This callback allows you to perform side effects in response to state updates.
<html>
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
let { state } = lemonade;
function Test() {
let status = state(true, (newValue, oldValue) => {
// Value has been re-defined
console.log(newValue)
});
const update = () => {
status.value = ! status.value;
}
return render => render`<div>
<p><Switch value="${status}" /></p>
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
lemonade.render(Test, document.getElementById('root'));
</script>
</html>
import { state } from 'lemonadejs';
export default function Test() {
let status = state(true, (newValue, oldValue) => {
// Value has been re-defined
console.log(newValue)
});
const update = () => {
status.value = ! status.value;
}
return render => render`<div>
<Switch value="${status}" /><br/>
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
Reactivity in Objects
LemonadeJS prioritizes performance by using raw references for objects and arrays, avoiding the overhead of Proxy tracking. On those cases to trigger reactivity, use the refresh method to manually re-render the references in the view.
Reactive
Updating the entire state value will automatically trigger reactivity:
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
let { state } = lemonade;
function Test() {
// Default option
let palette = state([
["#001969","#233178","#394a87","#4d6396","#607ea4","#7599b3"],
["#00429d","#2b57a7","#426cb0","#5681b9","#6997c2","#7daeca"],
["#3659b8","#486cbf","#597fc5","#6893cb","#78a6d1","#89bad6"],
["#003790","#315278","#48687a","#5e7d81","#76938c","#8fa89a"]
]);
const updatePalette = function() {
palette.value = [
["#840f38","#b60718","#ff0c0c","#fd491c","#faa331","#ffc73a"],
["#5f0b28","#930513","#ef0000","#fa3403","#f9991b","#ffc123"],
["#370617","#6a040f","#d00000","#dc2f02","#f48c06","#ffba08"],
["#320615","#61040d","#bc0000","#c82a02","#db7f06","#efab00"],
];
}
return render => render`<div>
<input type="text" :ref="this.input" />
<Color palette="${palette}" :input="this.input" />
<input type="button" value="Update Palette" onclick="${updatePalette}" />
</div>`;
}
lemonade.render(Test, document.getElementById('root'));
</script>
</html>
import { state } from 'lemonadejs';
export default function Test() {
// Default option
let palette = state([
["#001969","#233178","#394a87","#4d6396","#607ea4","#7599b3"],
["#00429d","#2b57a7","#426cb0","#5681b9","#6997c2","#7daeca"],
["#3659b8","#486cbf","#597fc5","#6893cb","#78a6d1","#89bad6"],
["#003790","#315278","#48687a","#5e7d81","#76938c","#8fa89a"]
]);
const updatePalette = function() {
palette.value = [
["#840f38","#b60718","#ff0c0c","#fd491c","#faa331","#ffc73a"],
["#5f0b28","#930513","#ef0000","#fa3403","#f9991b","#ffc123"],
["#370617","#6a040f","#d00000","#dc2f02","#f48c06","#ffba08"],
["#320615","#61040d","#bc0000","#c82a02","#db7f06","#efab00"],
];
}
return render => render`<div>
<input type="text" :ref="this.input" />
<Color palette="${palette}" :input="this.input" />
<input type="button" value="Update Palette" onclick="${updatePalette}" />
</div>`;
}
Functional Update Pattern
The Functional Update pattern in LemonadeJS provides a way to update the state based on its previous values using a callback function. This pattern is handy when the new state depends on the current state.
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
let { state } = lemonade;
function Test() {
// Default option
let palette = state([
["#001969","#233178","#394a87","#4d6396","#607ea4","#7599b3"],
["#00429d","#2b57a7","#426cb0","#5681b9","#6997c2","#7daeca"],
["#3659b8","#486cbf","#597fc5","#6893cb","#78a6d1","#89bad6"],
["#003790","#315278","#48687a","#5e7d81","#76938c","#8fa89a"]
]);
const updatePalette = function() {
// Update a single value does not trigger reaction
palette.value = (prev) => {
prev[0][0] = '#000000';
return prev;
}
}
return render => render`<div>
<input type="text" :ref="this.input" />
<Color palette="${palette}" :input="this.input" />
<input type="button" value="Update Palette" onclick="${updatePalette}" />
</div>`;
}
lemonade.render(Test, document.getElementById('root'));
</script>
</html>
import { state } from 'lemonadejs';
export default function Test() {
// Default option
let palette = state([
["#001969","#233178","#394a87","#4d6396","#607ea4","#7599b3"],
["#00429d","#2b57a7","#426cb0","#5681b9","#6997c2","#7daeca"],
["#3659b8","#486cbf","#597fc5","#6893cb","#78a6d1","#89bad6"],
["#003790","#315278","#48687a","#5e7d81","#76938c","#8fa89a"]
]);
const updatePalette = function() {
// Update a single value does not trigger reaction
palette.value = (prev) => {
prev[0][0] = '#000000';
}
}
return render => render`<div>
<input type="text" :ref="this.input" />
<Color palette="${palette}" :input="this.input" />
<input type="button" value="Update Palette" onclick="${updatePalette}" />
</div>`;
}
Explicit State Synchronization
In specific performance-critical applications, applying multiple state changes is beneficial before triggering a single view refresh. LemonadeJS provides an explicit state synchronization pattern that gives developers precise control over when updates occur.
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
let { state } = lemonade;
function GameState() {
let game = state({
player: { x: 0, y: 0, health: 100, score: 0 },
enemies: [
{ id: 1, x: 10, y: 20, health: 50 },
{ id: 2, x: 30, y: 40, health: 50 }
]
});
const handleCollision = () => {
// Multiple state changes
game.value.player.health -= 10;
game.value.player.score += 50;
game.value.enemies[0].health -= 25;
// Single refresh triggers one re-render
this.refresh();
}
return render => render`<div>
<p>Player Health: ${game.value.player.health}</p>
<input type="button" onclick="${handleCollision}" value="Simulate Collision" />
</div>`;
}
lemonade.render(GameState, document.getElementById('root'));
</script>
</html>
import { state } from 'lemonadejs';
export default function Test() {
let game = state({
player: { x: 0, y: 0, health: 100, score: 0 },
enemies: [
{ id: 1, x: 10, y: 20, health: 50 },
{ id: 2, x: 30, y: 40, health: 50 }
]
});
const handleCollision = () => {
// Multiple state changes
game.value.player.health -= 10;
game.value.player.score += 50;
game.value.enemies[0].health -= 25;
// Single refresh triggers one re-render
this.refresh();
}
return render => render`<div>
<p>Player Health: ${game.value.player.health}</p>
<input type="button" onclick="${handleCollision}" value="Simulate Collision" />
</div>`;
}
Reactive Component Properties
Component properties defined on this
are automatically reactive when used in templates and accessible through the component hierarchy. The state
provides private, reactive values scoped within the component.
<script src="https://lemonadejs.com/v5/lemonade.js"></script>
<div id='root'></div>
<script>
function Test() {
// Default value
this.status = true;
// Method to toggle the value
const update = () => {
this.status = ! this.status;
}
return render => render`<div>
<div>test: ${this.status}</div>
<p><Switch :bind="${this.status}" /></p>
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
lemonade.render(Test, document.getElementById('root'));
</script>
</html>
export default function Test() {
// Default value
this.status = true;
// Method to toggle the value
const update = () => {
this.status = ! this.status;
}
return render => render`<div>
<div>test: ${this.status}</div>
<Switch :bind="${this.status}" /><br/>
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
Single Reference Binding Limitation
In LemonadeJS, updating a self property overrides the DOM attribute with a single reference value, discarding any interpolated text or additional references in the original binding.
export default function Test() {
let status = state(true);
const update = () => {
status.value = ! status.value;
}
// The result of value after the update will be only the boolean value removing the text.
return render => render`<div>
<Test value="Status: ${status}" />
<input type="button" value="Toggle" onclick="${update}"/>
</div>`
}
Comparison
A quick comparison with React.
Aspect | LemonadeJS State | React State |
---|---|---|
Declaration | State in LemonadeJS is defined as private, reactive properties within the component. | State in React is managed via the useState hook (functional components) or this.state (class components). |
Reactivity Mechanism | LemonadeJS employs micro-events tied to state properties for automatic reactivity. | React batches state updates and uses a virtual DOM to compute differences and update the real DOM. |
Mutability | State in LemonadeJS is mutable, with direct updates triggering DOM changes. | React state is immutable, requiring updates via setState or the useState updater function. |
Complexity | LemonadeJS offers a lightweight, straightforward approach without a virtual DOM. | React involves a more intricate system with a virtual DOM and reconciliation process. |
Usage | LemonadeJS state is private and encapsulated within the component’s scope. | React state can be shared across components using Context API or libraries like Redux. |
What's Next?
Explore how LemonadeJS handles dynamic references in templates.