| name | solid-core-lifecycle |
| description | SolidJS lifecycle: onMount for DOM access after mount, onCleanup for resource disposal, effect lifecycle management, component vs effect cleanup. |
| metadata | {"globs":["**/*lifecycle*","**/*mount*","**/*cleanup*"]} |
SolidJS Lifecycle Management
Complete guide to managing component and effect lifecycle in SolidJS. Understanding lifecycle is essential for proper resource cleanup and avoiding memory leaks.
onMount - Component Mounting
onMount runs code after components mount to the DOM. Perfect for accessing refs, setting up third-party libraries, and one-time initializations.
import { onMount } from "solid-js";
function MyComponent() {
let ref: HTMLButtonElement;
onMount(() => {
ref.disabled = true;
});
return <button ref={ref}>Focus me!</button>;
}
Key characteristics:
- Runs once after initial render
- Non-tracking (equivalent to
createEffect with no dependencies)
- Perfect for DOM access via refs
- Ideal for third-party library initialization
Use cases:
- Accessing DOM refs
- Initializing libraries (charts, maps, etc.)
- Setting up one-time event listeners
- Fetching initial data
onCleanup - Resource Disposal
onCleanup registers cleanup methods that execute when components unmount or tracking scopes dispose. Essential for preventing memory leaks.
import { createSignal, onCleanup } from "solid-js";
function Component() {
const [count, setCount] = createSignal(0);
const handleClick = () => setCount((value) => value + 1);
document.addEventListener("click", handleClick);
onCleanup(() => {
document.removeEventListener("click", handleClick);
});
return <main>Document has been clicked {count()} times</main>;
}
When cleanup runs:
- In components: When component unmounts
- In effects: When effect is disposed or recalculated
- In memos: When memo is disposed
- In roots: When root is disposed
Effect Lifecycle
Effects have their own lifecycle. Cleanup runs before effect re-executes:
import { createEffect, onCleanup, createSignal } from "solid-js";
function Component() {
const [id, setId] = createSignal(1);
createEffect(() => {
const currentId = id();
const subscription = subscribeToData(currentId);
onCleanup(() => {
subscription.unsubscribe();
});
});
return <button onClick={() => setId(id() + 1)}>Next</button>;
}
Cleanup execution order:
- Previous cleanup runs (if effect re-executes)
- Effect body runs
- New cleanup registered
Component Lifecycle vs Effect Lifecycle
Component lifecycle:
- Component function runs once on initialization
onMount runs after DOM mount
onCleanup runs on unmount
- No re-execution on state changes (fine-grained reactivity)
Effect lifecycle:
- Effect runs initially, then on dependency changes
- Cleanup runs before re-execution
- Cleanup runs on disposal
function Component() {
const [count, setCount] = createSignal(0);
console.log("Component initialized");
onMount(() => {
console.log("Component mounted");
});
onCleanup(() => {
console.log("Component cleanup");
});
createEffect(() => {
console.log("Effect runs:", count());
onCleanup(() => {
console.log("Effect cleanup");
});
});
return <button onClick={() => setCount(count() + 1)}>Count: {count()}</button>;
}
Cleanup Best Practices
1. Always Clean Up Event Listeners
function Component() {
const handleResize = () => {
};
window.addEventListener("resize", handleResize);
onCleanup(() => {
window.removeEventListener("resize", handleResize);
});
}
2. Clean Up Subscriptions
function Component() {
const [data, setData] = createSignal(null);
createEffect(() => {
const subscription = observable.subscribe((value) => {
setData(value);
});
onCleanup(() => {
subscription.unsubscribe();
});
});
}
3. Clean Up Intervals/Timeouts
function Component() {
createEffect(() => {
const interval = setInterval(() => {
}, 1000);
onCleanup(() => {
clearInterval(interval);
});
});
}
4. Clean Up AbortControllers
function Component() {
createEffect(() => {
const controller = new AbortController();
fetch("/api/data", { signal: controller.signal })
.then((res) => res.json())
.then(setData);
onCleanup(() => {
controller.abort();
});
});
}
5. Multiple Cleanups
You can register multiple cleanups - they all run in reverse order:
function Component() {
onMount(() => {
const listener1 = setupListener1();
const listener2 = setupListener2();
onCleanup(() => {
listener2.cleanup();
});
onCleanup(() => {
listener1.cleanup();
});
});
}
Common Patterns
Accessing Refs After Mount
function Component() {
let inputRef: HTMLInputElement;
onMount(() => {
inputRef.focus();
inputRef.select();
});
return <input ref={inputRef} />;
}
Third-Party Library Initialization
function ChartComponent() {
let chartContainer: HTMLDivElement;
let chartInstance: Chart;
onMount(() => {
chartInstance = new Chart(chartContainer, {
});
});
onCleanup(() => {
if (chartInstance) {
chartInstance.destroy();
}
});
return <div ref={chartContainer} />;
}
Conditional Cleanup
function Component() {
createEffect(() => {
let cleanup: (() => void) | undefined;
if (someCondition()) {
cleanup = setupSomething();
}
onCleanup(() => {
if (cleanup) {
cleanup();
}
});
});
}
Async Operations Cleanup
function Component() {
createEffect(() => {
let cancelled = false;
fetchData().then((data) => {
if (!cancelled) {
setData(data);
}
});
onCleanup(() => {
cancelled = true;
});
});
}
Anti-Patterns to Avoid
❌ Don't Access Refs Outside onMount
function Component() {
let ref: HTMLDivElement;
ref.focus();
return <div ref={ref} />;
}
function Component() {
let ref: HTMLDivElement;
onMount(() => {
ref.focus();
});
return <div ref={ref} />;
}
❌ Don't Forget to Clean Up
function Component() {
document.addEventListener("click", handleClick);
}
function Component() {
document.addEventListener("click", handleClick);
onCleanup(() => {
document.removeEventListener("click", handleClick);
});
}
❌ Don't Use onMount for Reactive Code
function Component() {
const [count, setCount] = createSignal(0);
onMount(() => {
console.log(count());
});
}
function Component() {
const [count, setCount] = createSignal(0);
createEffect(() => {
console.log(count());
});
}
Summary
- onMount: One-time setup after DOM mount
- onCleanup: Resource disposal on unmount/disposal
- Effect cleanup: Runs before effect re-executes
- Always clean up: Event listeners, subscriptions, intervals, timeouts
- Component runs once: Use effects for reactive code, not component body