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;
}