Async Effect

Async Effect 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 } from '@expressive/mvc';

const NAMES = [
  'Ada Lovelace',
  'Alan Turing',
  'Barbara Liskov',
  'Donald Knuth',
  'Edsger Dijkstra',
  'Frances Allen',
  'Grace Hopper',
  'Katherine Johnson',
  'Linus Torvalds',
  'Margaret Hamilton'
];

export default () => (
  <div className="container">
    <h1>Async Effect</h1>
    <p>
      An effect that returns a promise runs one at a time. Typing while a lookup
      is in flight starts nothing - the changes queue a single re-run with the
      latest <code>query</code> once it settles, so results never arrive out of
      order and lookups never overlap.
    </p>
    <Search />
    <small>
      The effect reads <code>query</code> by destructuring, which subscribes to
      it. Writes to <code>this</code> after the <code>await</code> land on the
      live instance.
    </small>
  </div>
);

class Search extends Component {
  query = '';
  results: string[] = [];
  keystrokes = 0;
  lookups = 0;

  protected new() {
    this.get(async ({ query }) => {
      this.lookups++;

      await new Promise((done) => setTimeout(done, 600));

      const match = query.toLowerCase();

      this.results = NAMES.filter((name) => name.toLowerCase().includes(match)).slice(0, 5);
    });
  }

  search(event: { currentTarget: { value: string } }) {
    this.keystrokes++;
    this.query = event.currentTarget.value;
  }

  render() {
    const { query, results, keystrokes, lookups } = this;

    return (
      <>
        <input placeholder="Search names" value={query} onInput={this.search} />
        <p className="tally">
          Keystrokes: {keystrokes} · Lookups: {lookups}
        </p>
        <ul className="results">
          {results.map((name) => (
            <li key={name}>{name}</li>
          ))}
        </ul>
      </>
    );
  }
}

App.css

.container input {
  width: 100%;
  max-width: 20rem;
  padding: var(--s2) var(--s3);
  font: inherit;
}

.tally {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.results {
  padding-left: var(--s5);
  min-height: 8rem;
}