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.