Skip to content

useRef

useRef declares a handle you bind to one component of this render, so other code can change it, measure it, or bind an event to it. Its module-scope pair is createRef.

useRef(): Ref

Call it in a component, then bind it with the ref prop:

src/quest-log.ts2
export const QuestList = (): Component => {
const list = useRef();
src/quest-log.ts2
<layer ref={list} x={2} y={2} w={{ fill: 22 }} h={{ fill: 4 }}
scrollHeight={QUESTS.length * ROW_H}>

None.

A Ref: a handle on one component of this render. A component written twice has two of them.

  • A ref is bound exactly once. Unbound, it addresses nothing (TS2E215); bound twice in one render, even in different arms, it is refused (TS2E214).
  • A ref names a component that exists. Read or written above its binding — in the render body, before the markup — it addresses a component that does not exist yet, and is refused (TS2E215). Read it in a handler, in an effect, or in markup below the binding.
  • A ref you were handed is for reading and writing through, never for binding. ref={target} on a Ref prop is refused (TS2E230).
  • A ref write survives an in-place update and not a rebuild. If a change must persist, it is state, not a ref — How updates happen.
refused/use-ref.ts2
const Refused = (): Component => {
const plate = useRef();
plate.hide = true; // the rect does not exist yet
return <rect ref={plate} w={52} h={20} color={Colors.WELL} fill />;
};

Every prop the bound element accepts, by the same name and with the same meaning: hide, color, text, font, sprite, transparency, scrollHeight, and so on — the element’s own page lists them. Three groups are written together, not one at a time:

  • ref.props = { … }: Several props in one write. Reach for it whenever a single write is refused (TS2E215, “cannot be set through a ref on its own”). The groups: { x, y } and { w, h } (geometry with its modes — both axes of a pair, and the values are what markup takes: a number, a keyword, { right: 4 }, or a conditional); { item, itemCount, itemCountMode? }; { align, valign, lineHeight }; { offsetX, offsetY, pitch, yaw, roll, zoom }. Any other prop may go in props too, alone or with others: plate.props = { hide: false, x: 'center', y: 0 }.
  • ref.options = [ … ]: The right-click menu, the same value the options prop takes. An assignment writes the slots it names and leaves the rest; the empty list is the clear.
  • ref.onClick = handler and every other event prop: bind a handler on a component you did not create. = null takes it off.
  • width, height: The size after layout. Writable too: ref.height = n pins the width to its current pixels; to keep fill, write ref.props = { w: 'fill', h: n }.
  • x, y: The position after layout. Writable too: ref.x = n pins both axes to pixels; to centre, write ref.props = { x: 'center', y: … }.
  • scrollX, scrollY: The current scroll offset. Writable; assigning moves the scroll, clamped into range.
  • scrollWidth, scrollHeight: The scrollable extent.
  • paramInt(ref), paramText(ref), setParamInt(ref, v), setParamText(ref, v): A per-component value the client keeps for you, read and written by a named parameter.

Every naming of a component reads these, not only a useRef() handle: a ComponentId parameter (target.width!), event.self, a module-scope createRef.

refused/use-ref-axis.ts2
onClick={() => { plate.props = { x: "center" }; }} />
src/chrome.ts2
export const Scrollbar = ({ content }: { content: Ref }): Component => {
src/quest-log.ts2
<Scrollbar content={list} />

A component cannot take a function; pass the Ref.

src/chrome.ts2
const scrollExtent: Int = content.scrollHeight;
const extent: Int = scrollExtent > 0 ? scrollExtent : content.height;

A ref you were handed can be read above your own markup.

src/marker.ts2
<text ref={readout} x={0} y={WELL_H + 6} w="fill" h={16}
text={`x ${plate.x} · ${plate.width} wide`}
font={Fonts.SMALL} valign="middle" shadow color={Colors.DIM} />
src/marker.ts2
if (mode === 0) { target.props = { x: 'left', y: 'center' }; }
if (mode === 1) { target.props = { x: 'center', y: 'center' }; }
if (mode === 2) { target.props = { x: 'right', y: 'center' }; }
src/marker.ts2
event.self.x = event.self.x + 8;
src/marker.ts2
if (mode === 2) { target.options = []; } else { target.options = ['Nudge right']; }

Binding an event on something you were handed

Section titled “Binding an event on something you were handed”
src/chrome.ts2
content.onScrollWheel = (event) => {
content.scrollY = content.scrollY + event.mouseY * WHEEL;
trackScroll(gutter, thumb, capTop, capBottom, content);
};
src/deaf.ts2
useInterval(() => { setTicks(ticks + 1); }, { everyMs: 200 }, host);

A ref handed to a hook says which component the timer or watch lives on. A hidden host does not run it — Timers.

Every useRef() must have a matching ref={…} in the same component’s markup. If you meant to hand the ref to a child, the child reads and writes through it; it does not bind it.

The component was rebuilt, and ref writes are lost. Either make the value a cell and read it in the markup, or move the conditional into a child so this component updates in place.

Through props, geometry is a pair: ref.props = { x: 'center', y: 'center' }. To move by pixels and pin, ref.x = n pins both axes.

The component does not exist yet. Move the read into the prop that needs it, below the binding, or into a handler.