set()
set() 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';
// `set` manages a slot beyond a plain field: a default value, a validator,
// or a change-effect - each just a callback, no useEffect, no deps array.
class Account extends Component {
// A default, plus a callback that vets every assignment. `throw false`
// rejects the update - the field never exceeds 12 characters.
name = set('guest', (next) => {
if (next.length > 12) throw false;
});
// The callback may return a cleanup, run before the next change - so a
// debounce falls out for free: each keystroke cancels the previous timer.
query = set('', (next) => {
this.result = 'typing…';
const timer = setTimeout(() => {
this.result = next ? `searching “${next}”` : 'idle';
}, 500);
return () => clearTimeout(timer);
});
result = 'idle';
render() {
const { name, query, result } = this;
return (
<div className="container">
<h1>Managed Slots</h1>
<label>
Display name <small>(max 12 chars — extra input is rejected)</small>
<input value={name} onChange={(e) => (this.name = e.target.value)} />
</label>
<label>
Search <small>(debounced 500ms by the callback’s cleanup)</small>
<input value={query} onChange={(e) => (this.query = e.target.value)} />
</label>
<p className="result">{result}</p>
</div>
);
}
}
export default () => <Account />;
App.css
.container label {
display: flex;
flex-direction: column;
gap: var(--s2);
width: 100%;
padding: var(--s4);
text-align: left;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r-lg);
}
.container label small {
font-weight: 400;
color: var(--muted);
}
.result {
align-self: stretch;
margin: 0;
padding: var(--s2) var(--s3);
font-family: var(--font-mono);
font-size: var(--t-sm);
color: var(--fg-soft);
background: var(--code-bg);
border: 1px solid var(--border);
border-radius: var(--r);
text-align: left;
}