React Hooks in SPFx: useRef
useRef patterns in SPFx: DOM references, mutable values, and performance considerations.
Why useRef in SPFx
useRef lets you:
- Access DOM elements (focus inputs, measure sizes, scroll)
- Hold mutable values that do not cause re-renders (timers, latest props)
- Keep cancellation tokens (AbortController) across renders and clean up reliably
Best Practices
1) Type your DOM refs and null-check
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return <input ref={inputRef} />;
2) Store timers/handles in refs and clean up
const intervalRef = useRef<number | null>(null);
useEffect(() => {
intervalRef.current = window.setInterval(() => console.log('tick'), 1000);
return () => {
if (intervalRef.current !== null) window.clearInterval(intervalRef.current);
};
}, []);
3) Latest value ref for event handlers
const latestQuery = useRef('');
const [query, setQuery] = useState('');
useEffect(() => { latestQuery.current = query; }, [query]);
function onSubmit() {
// Always reads the latest query without re-binding handlers
doSearch(latestQuery.current);
}
4) AbortController in ref for unmount-safe async
const acRef = useRef<AbortController | null>(null);
async function load() {
acRef.current?.abort(); // cancel previous
acRef.current = new AbortController();
try {
const res = await fetch(`${siteUrl}/_api/...`, { signal: acRef.current.signal });
const json = await res.json();
setItems(json.value);
} catch (e: any) {
if (e.name !== 'AbortError') setError(e.message);
}
}
useEffect(() => () => acRef.current?.abort(), []);
Anti‑Patterns
❌ Using refs as state
Refs do not trigger renders. Don’t store UI values that must re-render.
❌ Assuming ref is set synchronously
Ref assignment happens after render. Null-check or handle in useEffect.
❌ Mutating complex objects via ref expecting React to notice
React won’t track ref mutations — prefer state for UI-driven data.
SPFx-specific patterns
Measure and adjust layout
const boxRef = useRef<HTMLDivElement | null>(null);
const [width, setWidth] = useState(0);
useEffect(() => {
const el = boxRef.current;
if (!el) return;
setWidth(el.getBoundingClientRect().width);
}, []);
return <div ref={boxRef} style={{ width }}>{/* content */}</div>;
Scroll into view
const targetRef = useRef<HTMLDivElement | null>(null);
function scrollToTarget() { targetRef.current?.scrollIntoView({ behavior: 'smooth' }); }