Lifecycle
Lifecycle demo built with Expressive MVC - the complete source below runs as an editable sandbox when JavaScript is enabled.
App.tsx
import './App.css';
import Button from './common/Button';
import { Component, get, has, ref } from '@expressive/react';
export default () => (
<div className="container">
<h1>Lifecycle</h1>
<p>
Three seams, three phases. <code>new()</code> runs at construction,
synchronously and during server render too, so it belongs to setup the
instance carries with it. <code>mount()</code> runs at commit and only on
the client - where timers, listeners and anything reaching for{' '}
<code>window</code> go. <code>ref()</code> fires when its element attaches.
</p>
<Demo />
<small>
Toggle the probe to watch each seam fire, then unwind in reverse. The
transcript lives on the demo, which outlives the probe writing to it.
</small>
</div>
);
class Demo extends Component {
showing = true;
entries = has<string>();
log(note: string) {
this.entries.push(note);
}
render() {
const { showing, entries } = this;
return (
<>
{showing && <Probe />}
<div className="row">
<Button primary onClick={() => (this.showing = !showing)}>
{showing ? 'Unmount' : 'Mount'}
</Button>
<Button onClick={() => entries.clear()}>Clear</Button>
</div>
<ol className="trace">
{entries.map((note, i) => (
<li key={i}>{note}</li>
))}
</ol>
</>
);
}
}
class Probe extends Component {
demo = get(Demo);
width = 0;
ticks = 0;
protected new() {
this.demo.log('new() · constructed');
return () => this.demo.log('new() cleanup · destroyed');
}
mount() {
this.demo.log('mount() · committed');
const measure = () => (this.width = window.innerWidth);
const timer = setInterval(() => this.ticks++, 1000);
measure();
window.addEventListener('resize', measure);
return () => {
this.demo.log('mount() cleanup · unmounted');
clearInterval(timer);
window.removeEventListener('resize', measure);
};
}
box = ref<HTMLDivElement>(() => {
this.demo.log('ref() · element attached');
return () => this.demo.log('ref() · element detached');
});
render() {
const { width, ticks } = this;
return (
<div className="probe" ref={this.box}>
<strong>{width}px</strong>
<small>alive {ticks}s</small>
</div>
);
}
}
App.css
.row {
display: flex;
gap: var(--s2);
}
.probe {
display: flex;
flex-direction: column;
gap: var(--s1);
width: 100%;
padding: var(--s4);
background: var(--surface);
border: 1px solid var(--accent);
border-radius: var(--r);
}
.probe strong {
font-family: var(--font-mono);
font-size: var(--t-xl);
color: var(--accent);
}
.trace {
width: 100%;
margin: 0;
padding: 0;
list-style: none;
text-align: left;
font-family: var(--font-mono);
font-size: var(--t-xs);
border: 1px solid var(--border);
border-radius: var(--r);
overflow: hidden;
}
.trace:empty::after {
content: 'nothing logged yet';
display: block;
padding: var(--s3);
color: var(--muted);
}
.trace li {
padding: var(--s2) var(--s3);
border-radius: 0;
cursor: default;
color: var(--fg-soft);
border-top: 1px solid var(--border);
}
.trace li:first-child {
border-top: none;
}
.trace li:hover {
background: none;
}