Lifecycle
Run code when components mount and unmount. Set up subscriptions, fetch data, and clean up resources.
onMount
Run code once when a component first renders:
import { onMount } from 'what-framework';
import Chart from 'chart.js/auto';
function LineChart({ data }) {
let canvas;
onMount(() => {
// The ref has fired and the canvas is in the DOM
const chart = new Chart(canvas, { data });
console.log('Chart initialized');
});
return <canvas ref={el => canvas = el} />;
}
Note the component is LineChart, not Chart. Naming it after the library would shadow the import, so new Chart(...) would re-enter the component instead of constructing the chart.
Use onMount for:
- DOM measurements (element sizes, positions)
- Setting up third-party libraries
- Initial data fetching
- Animations on enter
Call these in the component body
onMount and onCleanup resolve the current component from the render stack, so they must be called synchronously in the component body, never inside a callback, an effect, or after an await. In particular, calling onCleanup from inside an onMount callback throws, and the error is only logged to the console.
onMount also ignores whatever you return from it. Teardown belongs in onCleanup.
onCleanup
Register cleanup code to run when the component unmounts:
import { onMount, onCleanup } from 'what-framework';
function WebSocketChat({ roomId }) {
let socket;
onMount(() => {
socket = new WebSocket(`wss://chat.example.com/${roomId}`);
socket.onmessage = (e) => {
console.log('Message:', e.data);
};
});
onCleanup(() => {
// Close connection when component unmounts
socket?.close();
});
return <div className="chat">...</div>;
}
Use onCleanup for:
- Removing event listeners
- Closing connections (WebSocket, EventSource)
- Clearing timers/intervals
- Canceling pending requests
- Cleaning up third-party library instances
Common Patterns
Event Listeners
function WindowSize() {
const size = signal({ width: 0, height: 0 });
// Declared in the body so both hooks can see it
const updateSize = () => {
size.set({
width: window.innerWidth,
height: window.innerHeight,
});
};
onMount(() => {
updateSize();
window.addEventListener('resize', updateSize);
});
onCleanup(() => {
window.removeEventListener('resize', updateSize);
});
return <p>{() => `${size().width} x ${size().height}`}</p>;
}
Timers
function Clock() {
const time = signal(new Date());
let id;
onMount(() => {
id = setInterval(() => {
time.set(new Date());
}, 1000);
});
// onMount discards its return value, so clear the timer here
onCleanup(() => clearInterval(id));
return <p>{() => time().toLocaleTimeString()}</p>;
}
Third-Party Libraries
function LeafletMap({ lat, lng }) {
let container;
let map;
onMount(() => {
map = L.map(container).setView([lat, lng], 13);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
});
onCleanup(() => {
map?.remove();
});
return <div ref={el => container = el} style={{ height: '400px' }} />;
}
useEffect Alternative
For React-style lifecycle, use useEffect:
import { useEffect } from 'what-framework';
// The parent passes the signal itself: <Profile userId={userIdSignal} />
function Profile({ userId }) {
const user = signal(null);
// Runs on mount and again whenever userId changes
useEffect(() => {
fetch(`/api/users/${userId()}`)
.then(r => r.json())
.then(data => user.set(data));
// Cleanup (optional)
return () => {
console.log('Cleanup');
};
}, [userId]); // Dependencies: signal accessors only
return <div>{() => user()?.name}</div>;
}
A deps array only tracks entries that are signal accessors. Pass the value ([userId()], or a plain prop) and the effect runs once and can never re-run, because in a run-once component a plain value never changes. What warns you in dev when it sees one. You can also drop the deps array entirely: useEffect(fn) auto-tracks every signal read inside the callback, exactly like effect.
Comparison
| API | When to Use |
|---|---|
onMount |
Setup that runs once on mount |
onCleanup |
Cleanup that runs on unmount |
useEffect |
React-style effect with dependencies |
effect |
Reactive effect (auto-tracks signals). The only entry here that does not clean itself up: pass its returned disposer to onCleanup. |
Which to choose?
Use onMount/onCleanup for one-time setup. Use effect for reactive side effects that depend on signals. Use useEffect for React compatibility.
onMount and onCleanup are client-only. On a server render the component context is closed as soon as the component returns, so neither callback ever fires, and on a hydrated page they run at hydration rather than at server render.