get()
get() 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, Provider } from '@expressive/react';
// The same instruction reaches both directions of the context tree.
// Downstream: get(Candidate, true) collects every Candidate mounted below,
// and the array tracks them as they mount and unmount - no registration.
class Poll extends State {
candidates = get(Candidate, true);
choice = '';
get leader() {
return this.choice || '—';
}
}
// Upstream: get(Poll) hands each Candidate the poll it lives under, so a
// click writes the shared choice and every candidate restyles.
class Candidate extends Component {
poll = get(Poll);
name = '';
render() {
const { poll, name } = this;
return (
<li
className={poll.choice === name ? 'candidate chosen' : 'candidate'}
onClick={() => (poll.choice = name)}>
{name}
</li>
);
}
}
// A separate consumer reads the collected array - it re-renders as the
// roster grows or shrinks, and when the shared choice changes.
function Tally() {
const { candidates, leader } = Poll.get();
return (
<p className="tally">
{candidates.length} on the ballot · chose <b>{leader}</b>
</p>
);
}
export default class App extends Component {
roster = ['Ada', 'Alan', 'Grace'];
draft = '';
add() {
const name = this.draft.trim() || `Guest ${this.roster.length + 1}`;
this.roster = [...this.roster, name];
this.draft = '';
}
render() {
const { roster, draft } = this;
return (
<div className="container">
<h1>Context Collection</h1>
<Provider for={Poll}>
<Tally />
<ul className="ballot">
{roster.map((name) => (
<Candidate key={name} name={name} />
))}
</ul>
<form
className="add"
onSubmit={(e) => {
e.preventDefault();
this.add();
}}>
<input
value={draft}
placeholder={`Guest ${roster.length + 1}`}
onChange={(e) => (this.draft = e.target.value)}
/>
<button type="submit">Add</button>
</form>
</Provider>
</div>
);
}
}
App.css
.ballot {
gap: var(--s2);
}
.candidate {
padding: var(--s3);
text-align: center;
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);
}
.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;
}