Computed
Computed demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.
App.tsx
import './App.css';
import { Component } from '@expressive/react';
// Beyond single values: a getter is a *computed* property. It tracks
// whatever it reads and recomputes only when those inputs change - no
// useMemo, no dependency array. Getters may even read other getters.
class TipCalculator extends Component {
bill = 50;
tipPercent = 18;
get tip() {
return (this.bill * this.tipPercent) / 100;
}
get total() {
return this.bill + this.tip; // depends on another computed
}
render() {
const { bill, tipPercent, tip, total } = this;
return (
<div className="container">
<h1>Computed</h1>
<label>
Bill
<input
type="number"
value={bill}
onChange={(e) => (this.bill = +e.target.value)}
/>
</label>
<label>
Tip: {tipPercent}%
<input
type="range"
min={0}
max={30}
value={tipPercent}
onChange={(e) => (this.tipPercent = +e.target.value)}
/>
</label>
{/* tip and total recompute on their own when either input changes */}
<p className="result">
Tip <b>${tip.toFixed(2)}</b> · Total <b>${total.toFixed(2)}</b>
</p>
</div>
);
}
}
// You could export TipCalculator directly too.
// It's renderable like any other component.
export default () => <TipCalculator />;
App.css
.container label {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-bottom: 1rem;
color: var(--fg);
}
.container input[type='number'] {
width: 8rem;
padding: 0.4rem 0.5rem;
font: inherit;
}
.result {
font-size: var(--t-lg);
color: var(--fg);
}