set() Computed
set() 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, set } from '@expressive/react';
export default () => (
<div className="container">
<h1>Computed</h1>
<p>
Declare a parameter and the same instruction becomes a computed slot -{' '}
<code>set(self => …)</code> re-runs whenever a field it read is updated,
where <code>set(() => …)</code> would have run once and cached. Arity is
the whole difference.
</p>
<Invoice />
<small>
Because a derivation is a value rather than a getter, it can be factored out
and reused - <code>money()</code> below builds two of these fields, which no
pair of getters could share.
</small>
</div>
);
class Invoice extends Component {
hours = 12;
rate = 85;
discount = 10;
subtotal = set((self: Invoice) => self.hours * self.rate);
due = set((self: Invoice) => self.subtotal * (1 - self.discount / 100));
gross = money((self: Invoice) => self.subtotal);
net = money((self: Invoice) => self.due);
render() {
const { hours, rate, discount, gross, net } = this;
return (
<section className="invoice">
<label>
Hours
<input
type="number"
value={hours}
onChange={(e) => (this.hours = +e.target.value)}
/>
</label>
<label>
Rate
<input
type="number"
value={rate}
onChange={(e) => (this.rate = +e.target.value)}
/>
</label>
<label>
Discount {discount}%
<input
type="range"
max={50}
value={discount}
onChange={(e) => (this.discount = +e.target.value)}
/>
</label>
<footer>
<span>{gross}</span>
<b>{net}</b>
</footer>
</section>
);
}
}
function money<T>(pick: (self: T) => number) {
return set((self: T) => `$${pick(self).toFixed(2)}`);
}
App.css
.invoice {
display: flex;
flex-direction: column;
gap: var(--s4);
width: 100%;
padding: var(--s4);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
}
.invoice label {
display: flex;
flex-direction: column;
gap: var(--s2);
text-align: left;
font-size: var(--t-sm);
color: var(--muted);
}
.invoice footer {
display: flex;
align-items: baseline;
justify-content: space-between;
padding-top: var(--s3);
border-top: 1px solid var(--border);
font-family: var(--font-mono);
}
.invoice footer span {
color: var(--muted);
text-decoration: line-through;
}
.invoice footer b {
font-size: var(--t-xl);
color: var(--accent);
}