Async

Async demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.

App.tsx

import './App.css';

import { Control } from './Control';

const App = () => {
  // Control is a State, separating concerns of this component.
  // This focuses on presentation, while Control focuses on behavior.
  const { agent, dead, getNewAgent, remaining } = Control.use();

  if (dead === true)
    return (
      <div className="container">
        <div className="emoji">🙀💥</div>
        <h2>Unfortunately, the cat exploded.</h2>
      </div>
    );

  if (dead === false)
    return (
      <div className="container">
        <div className="emoji">😸👍</div>
        <h2>Oh, the cat did not explode.</h2>
      </div>
    );

  return (
    <div className="container">
      <h1>Async Example</h1>
      <div className="timer">
        <h1 className="box">📦</h1>
        <p>
          <b>Agent {agent}</b>, we need you to defuse the bomb!
        </p>
        <p>
          If you can't do it in <span className='seconds'>{remaining}</span> seconds, Schrödinger's cat may or
          may not die. But there's still time!
        </p>
        <p>
          <button onClick={getNewAgent}>Tap another agent</button>
          if you think they can do it.
        </p>
      </div>
    </div>
  );
};

export default App;

App.css

.timer {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 400px;
  gap: var(--s3);
}

.seconds {
  width: 1.2em;
  display: inline-block;
}

.timer p {
  margin: 0;
}

.timer button {
  margin: 0 var(--s2);
}

.box {
  margin: 0;
  font-size: 4rem;
  line-height: 1;
}

.emoji {
  font-size: 2rem;
  line-height: 1;
}

Control.tsx

import State from '@expressive/react';

export class Control extends State {
  // Properties set starting values; assignments dispatch updates.
  agent = 'Bond';
  remaining = 30;
  dead?: boolean = undefined;

  // new() runs once when ready. The returned function runs on
  // teardown - handy for clearing timers or subscriptions.
  protected new() {
    const timer = setInterval(() => {
      if (--this.remaining > 0) return;

      this.dead = Math.random() > 0.5;
      clearInterval(timer);
    }, 1000);

    return () => clearInterval(timer);
  }

  // Async methods assign to `this` - no thunks needed.
  async getNewAgent() {
    const res = await fetch('https://randomuser.me/api?nat=gb&results=1');
    const data = await res.json();

    this.agent = data.results[0].name.last;
  }
}