React Hooks in SPFx: useRef

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' }); }