cleanup

Registers a callback that runs when the current tracking scope is disposed — typically a parent component unmount, render's returned disposer being called, or a re-run memo recreating its scope. Pair it with anything you set up imperatively (timers, listeners, third-party instances) so it doesn't outlive the scope.

Callbacks run in reverse registration order (LIFO), and child scopes dispose before their parents. Don't rely on the scope's DOM having been detached yet inside a cleanup — in most scopes the nodes leave the document at the end of disposal, after the cleanups (keyed <For> rows are the exception: their nodes detach first). cleanup returns the same fn it was given.

Arguments

name type description
fn () => void function to run once the tracking scope is disposed

Returns: the same fn.

Examples

Clear a timer

A setInterval lives outside the reactive graph, so it must be torn down by hand. Registering clearInterval with cleanup ties the timer's lifetime to the component's.

import { cleanup, render, signal } from 'pota'

function Clock() {
	const now = signal(new Date())

	const id = setInterval(() => now.write(new Date()), 1000)
	cleanup(() => clearInterval(id))

	return <p>{() => now.read().toLocaleTimeString()}</p>
}

render(Clock)

Detach a manual listener

Anything attached imperatively to window or document outlives the component unless you remove it. cleanup runs when the owner disposes, so the listener goes away with the component.

import { cleanup, render, signal } from 'pota'

function Keys() {
	const last = signal('')

	const onKey = e => last.write(e.key)
	window.addEventListener('keydown', onKey)
	cleanup(() => window.removeEventListener('keydown', onKey))

	return <p>last key: {last.read}</p>
}

render(Keys)

Disposal ordering

Child scopes dispose before their parents — the deepest cleanups fire first. Unmount the tree and the cleanup: lines appear from the inside out: Child before Parent.

import { cleanup, render, signal } from 'pota'
import { Show } from 'pota/components'

const log = signal('mounted — unmount to see disposal order')

function Child() {
	cleanup(() => log.update(s => s + 'cleanup: Child\n'))
	return <p>child</p>
}

function Parent() {
	cleanup(() => log.update(s => s + 'cleanup: Parent'))
	return (
		<main>
			<Child />
		</main>
	)
}

function App() {
	const show = signal(true)

	return (
		<div>
			<button
				on:click={() => {
					log.write('')
					show.update(v => !v)
				}}
			>
				mount / unmount
			</button>
			<Show when={show.read}>
				<Parent />
			</Show>
			<pre>{log.read}</pre>
		</div>
	)
}

render(App)