use:ref

use:ref hands you a reference to the element. Its value is a function that receives the element, or an array (any depth) of such functions when more than one consumer needs the handle. The canonical producer is ref, a tiny signal-function: call r(node) to write, r() to read — so the reference works inside effects and memos.

The ref is written as soon as the element is created, before it is inserted into the document (static children are already cloned in; dynamic expression children fill in after). Layout-dependent properties like clientWidth therefore return 0 at ref time. For work that needs the element connected, run it inside ready or use use:connected.

Because the value is just a function (node) => void, the same attribute also attaches ref factories — this is how the built-in pota/use/* helpers wire behavior onto an element without a plugin registry.

Arguments

name type description
value fn | fn[] (any depth) function(s) called synchronously with the element at creation time

Each function runs in array order, synchronously, at element creation — before the element is inserted into the document.

Examples

Read a ref in an effect

ref() returns a signal-function the renderer assigns synchronously at element creation; reading button() inside an effect re-runs when it is written.

import { effect, ref, render, signal } from 'pota'

function App() {
	const button = ref()
	const log = signal('')

	effect(() => {
		if (button()) {
			log.write(`button: ${button().tagName}`)
		}
	})

	return (
		<div>
			<button
				name="button"
				use:ref={button}
				on:click={() => log.write(`clicked: ${button().tagName}`)}
			>
				button
			</button>
			<p>{log.read}</p>
		</div>
	)
}

render(App)

Focus on mount

Imperative work that requires the node to be in the DOM (focus, measure, integrate a third-party widget) belongs inside ready, which fires once the renderer has mounted everything.

import { ready, ref, render } from 'pota'

function App() {
	const input = ref()

	ready(() => input().focus())

	return (
		<div>
			<label>type here:</label>
			<input use:ref={input} />
		</div>
	)
}

render(App)

Ref at mount time

use:ref writes the reference at element creation, before the node is connected. To have the reference land at mount time instead, pass the same ref signal to use:connected — it takes the same signal-as-callback and fires once the element is in the document, so button() reads a connected node.

import { effect, ref, render, signal } from 'pota'

function App() {
	const button = ref()
	const log = signal('')

	effect(() => {
		if (button()) log.write(`connected: ${button().tagName}`)
	})

	return (
		<div>
			<button
				use:connected={button}
				on:click={() => log.write(`clicked: ${button().tagName}`)}
			>
				button
			</button>
			<p>{log.read}</p>
		</div>
	)
}

render(App)

Multiple refs on one element

use:ref accepts a single ref or an array — every entry receives the element. Useful when one place creates the element and another (a parent component, an external library wrapper) also needs a handle. Both refs read the same node.

import { ready, ref, render, signal } from 'pota'

function App() {
	const localRef = ref()
	const externalRef = ref()
	const log = signal('')

	ready(() => {
		log.write(
			`local sees ${localRef().tagName}, external sees ${externalRef().tagName}, same node? ${localRef() === externalRef()}`,
		)
	})

	return (
		<div>
			<input
				use:ref={[localRef, externalRef]}
				placeholder="bound to two refs"
			/>
			<p>{log.read}</p>
		</div>
	)
}

render(App)

Composable behaviors (ref factories)

Anything that's a function (node) => void works as a ref. To parameterize a behavior, write a factory that returns one. This is how the built-in pota/use/* helpers (clickOutside, clipboard, fullscreen, …) are built — and how you add your own without a plugin registry. Cleanup is automatic: addEvent, cleanup, and use:connected inside a ref body all dispose with the element.

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

const longPress =
	(handler, ms = 600) =>
	node => {
		let timer
		const start = () => {
			timer = setTimeout(handler, ms)
		}
		const stop = () => clearTimeout(timer)

		addEvent(node, 'pointerdown', start)
		addEvent(node, 'pointerup', stop)
		addEvent(node, 'pointerleave', stop)

		cleanup(stop)
	}

function App() {
	return (
		<button use:ref={longPress(() => alert('long pressed!'))}>
			hold me
		</button>
	)
}

render(App)

Composing many factories

Pass an array (any depth) of refs — every entry receives the element, in array order, synchronously at creation. Mix bare functions with factories from pota/use/*.

import { ready, render, signal } from 'pota'
import { clickOutside } from 'pota/use/clickoutside'
import { preventEnter } from 'pota/use/form'

const autoFocus = node => ready(() => node.focus())

function App() {
	const log = signal('')

	const logMount = label => node =>
		log.write(`${label} attached: ${node.tagName}`)

	return (
		<div>
			<input
				use:ref={[
					autoFocus,
					preventEnter,
					clickOutside(() => log.write('clicked outside')),
					logMount('input'),
				]}
				placeholder="type here"
			/>
			<p>{log.read}</p>
		</div>
	)
}

render(App)