Fetch
Fetch demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.
App.tsx
import './App.css';
import { Query } from './Query';
// Extending Query gives us all the request bookkeeping for free.
// We just supply `request()` - the actual endpoint-specific work -
// and inherit waiting/error tracking, run, and reset.
class HelloQuery extends Query {
async request() {
const res = await fetch('https://randomuser.me/api?nat=us&results=1');
const { first, last } = (await res.json()).results[0].name;
return `Hello ${first} ${last}`;
}
}
const Example = () => {
// `.use()` creates an instance scoped to App.
// Destructuring subscribes us to those fields.
const { response, error, waiting, reset, run } = HelloQuery.use();
if (response)
return (
<p className="fetch-result">
<span>Server said: {response}</span>
<button onClick={reset}>Reset</button>
</p>
)
if (error)
return (
<p className="fetch-result">
<span>Error: {error.message}</span>
<button onClick={reset}>Reset</button>
</p>
)
if (waiting)
return <p className="fetch-status">Sent. Waiting on response...</p>
return (
<button className="fetch-action" onClick={run}>Say hello to server</button>
)
};
export default () => (
<div className="container">
<h1>Fetch Example</h1>
<Example />
</div>
);
App.css
.fetch-action {
align-self: center;
}
.fetch-result {
display: flex;
align-items: center;
justify-content: center;
gap: var(--s3);
margin: 0;
color: var(--fg-soft);
}
.fetch-status {
margin: 0;
color: var(--muted);
}
Query.ts
import State from '@expressive/react';
// Most async work has the same shape - we wait for a response, get
// something back, sometimes get an error. Query handles that scaffolding
// so any specific endpoint only plugs in the part that's actually different.
export abstract class Query extends State {
// Three flags covering the lifecycle of a request.
// A component reading any of these refreshes when it changes.
response?: any = undefined;
error?: Error = undefined;
waiting = false;
// Subclasses supply the actual fetch. Whatever you return
// becomes `response`; throw to populate `error`.
abstract request(): Promise<any>;
// Call `run()` to kick things off. Assignments to `this` along the
// way refresh anyone subscribed - no manual notify, no setState.
async run() {
this.waiting = true;
try {
this.response = await this.request();
} catch (error) {
if (error instanceof Error) this.error = error;
} finally {
this.waiting = false;
}
}
reset() {
this.response = undefined;
this.error = undefined;
}
}