get() Downstream
get() Downstream demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.
App.tsx
import './App.css';
import State, { Component, get, has, Provider } from '@expressive/react';
export default () => (
<div className="container">
<h1>Downstream</h1>
<p>
A <code>true</code> flips the lookup around:{' '}
<code>get(Candidate, true)</code> collects every candidate mounted below,
and the array tracks arrivals and departures on its own - nothing registers
itself, nothing unregisters.
</p>
<Provider for={Poll}>
<Tally />
<Ballot />
</Provider>
<small>
A callback vets each arrival. Write-ins return <code>false</code> from it, so
they render and can be chosen - they just never join the roster the tally
counts.
</small>
</div>
);
class Poll extends State {
candidates = get(Candidate, true, (candidate) => !candidate.writeIn);
choice = '';
}
class Ballot extends Component {
slate = has(['Ada', 'Alan', 'Grace']);
writeIns = has<string>();
draft = '';
submit() {
this.writeIns.push(this.draft.trim() || 'Anonymous');
this.draft = '';
}
render() {
const { slate, writeIns, draft } = this;
return (
<>
<ul className="ballot">
{slate.map((name, i) => (
<Candidate key={name} name={name} remove={() => slate.pop(i)} />
))}
{writeIns.map((name, i) => (
<Candidate
key={'+' + name + i}
name={name}
writeIn
remove={() => writeIns.pop(i)}
/>
))}
</ul>
<form
className="add"
onSubmit={(e) => {
e.preventDefault();
this.submit();
}}>
<input
value={draft}
placeholder="Add a write-in"
onChange={(e) => (this.draft = e.target.value)}
/>
<button type="submit">Add</button>
</form>
</>
);
}
}
class Candidate extends Component {
poll = get(Poll);
name = '';
writeIn = false;
remove = () => {};
render() {
const { poll, name, writeIn } = this;
return (
<li
className={poll.choice === name ? 'candidate chosen' : 'candidate'}
onClick={() => (poll.choice = name)}>
<span>{name}</span>
{writeIn && <em>write-in</em>}
<button onClick={() => this.remove()}>×</button>
</li>
);
}
}
const Tally = () => {
const { candidates, choice } = Poll.get();
return (
<p className="tally">
{candidates.length} on the roster · chose <b>{choice || '—'}</b>
</p>
);
};
App.css
.ballot {
gap: var(--s2);
}
.candidate {
display: flex;
align-items: center;
gap: var(--s2);
padding: var(--s2) var(--s3);
font-weight: 500;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r);
transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.candidate:hover {
border-color: var(--border-2);
background: var(--surface-2);
}
.candidate.chosen {
color: var(--accent);
background: var(--accent-soft);
border-color: var(--accent);
}
.candidate span {
flex: 1;
text-align: left;
}
.candidate em {
font-size: var(--t-xs);
font-style: normal;
color: var(--muted);
}
.candidate button {
padding: 0 var(--s2);
line-height: 1.4;
color: var(--muted);
background: transparent;
border-color: transparent;
}
.candidate button:hover {
color: var(--fg);
}
.tally {
margin: 0;
padding: var(--s2) var(--s4);
font-size: var(--t-sm);
color: var(--muted);
background: var(--surface);
border: 1px solid var(--border);
border-radius: 999px;
}
.tally b {
color: var(--fg);
}
.add {
display: flex;
gap: var(--s2);
width: 100%;
}
.add button {
flex: 0 0 auto;
}