Skip to main content

useRef, DOM Access & getRef

useRef holds a mutable value that persists across renders without triggering re-renders when updated. In Dim it also powers cross-component DOM access via getRef.


Basic useRef​

import { define, html, css, useRef, useEffect, useStyle } from "../core/dim.ts";

const SearchBox = (props, { useRef, useEffect, html, css, useStyle }) => {
const inputRef = useRef(null);

useEffect(() => {
inputRef.current?.focus();
}, []);

useStyle(css`
input { width: 100%; padding: 0.5rem; }
`);

return html`
<input
ref="${inputRef}"
type="search"
placeholder="Search…"
/>
`;
};

Bind a ref in templates with Lit’s ref="${ref}" directive on the element you need to access.

Default ref target​

If you call useRef() without an initial value, current defaults to the component instance (the custom element). This supports legacy getRef lookups but is rarely what you want for DOM access — pass null explicitly:

const inputRef = useRef(null);

Mutable values without re-renders​

const intervalRef = useRef(null);
const renderCountRef = useRef(0);

renderCountRef.current += 1; // does not re-render

useEffect(() => {
intervalRef.current = setInterval(tick, 1000);
return () => clearInterval(intervalRef.current);
}, []);

Use refs for timers, previous value tracking, and imperative handles — not UI state (use useState).


querySelector in sharedDependencies​

Each component receives a shadow-root-scoped querySelector:

const Panel = (props, { querySelector, html }) => {
const focusFooter = () => {
querySelector(".footer-btn")?.focus();
};

return html`
<div>
<button class="footer-btn">Action</button>
<button @click="${focusFooter}">Focus footer</button>
</div>
`;
};

Prefer useRef when you own the element; use querySelector for ad-hoc queries inside your shadow tree.


getRef — cross-component access​

getRef finds another custom element in your shadow tree and returns that component’s first useRef .current value:

const Parent = (props, { getRef, html }) => {
const readChildValue = () => {
const childInput = getRef("child-field");
console.log(childInput?.value);
};

return html`
<div>
<child-field></child-field>
<button @click="${readChildValue}">Read child</button>
</div>
`;
};

getRef accepts a CSS selector matching the child custom element tag. The child must expose a useRef whose .current points at the DOM node you need.

This is an escape hatch — prefer .props callbacks for parent ↔ child communication when possible.


Focus management pattern​

const Modal = (props, { useRef, useEffect, html }) => {
const dialogRef = useRef(null);

useEffect(() => {
if (props.open) {
dialogRef.current?.focus();
}
}, [props.open]);

if (!props.open) return html``;

return html`
<div class="modal" ref="${dialogRef}" tabindex="-1">
${props.children}
</div>
`;
};

Pair with .props for open and onClose from the parent.


useRef vs useState​

useRefuseState
Updates trigger re-renderNoYes
Value stored on.currenttuple index 0
Survives rendersYesYes
Good forDOM nodes, timers, mutable countersUI-visible data

Conclusion​

useRef covers imperative DOM work inside functional web components. querySelector and getRef extend that to shadow-tree queries and rare cross-element access.