How do you use refs for DOM interaction in React? What are the different types of refs?
5 minintermediatereactrefsdominteraction
Quick Answer
Refs provide a way to access DOM elements or component instances directly. They're essential for DOM manipulation, focus management, and integrating with third-party libraries.
Detailed Answer
How do you use refs for DOM interaction in React? What are the different types of refs?
Answer:
Refs provide a way to access DOM elements or component instances directly. They're essential for DOM manipulation, focus management, and integrating with third-party libraries.
Types of Refs:
1. useRef Hook (Most Common):
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState('');
const focusInput = () => {
inputRef.current?.focus();
};
const selectAll = () => {
inputRef.current?.select();
};
return (
<div>
<input
ref={inputRef}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Type something..."
/>
<button onClick={focusInput}>Focus Input</button>
<button onClick={selectAll}>Select All</button>
</div>
);
}
2. Callback Refs:
function CallbackRefExample() {
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null);
const inputRef = useCallback((node: HTMLInputElement | null) => {
if (node) {
setInputElement(node);
node.focus(); // Auto-focus when element is created
}
}, []);
const measureElement = () => {
if (inputElement) {
const rect = inputElement.getBoundingClientRect();
console.log('Input dimensions:', rect);
}
};
return (
<div>
<input ref={inputRef} placeholder="Auto-focused input" />
<button onClick={measureElement}>Measure Input</button>
</div>
);
}
3. String Refs (Legacy - Don't Use):
// DON'T USE - Legacy pattern
class LegacyComponent extends Component {
render() {
return <input ref="myInput" />;
}
componentDidMount() {
this.refs.myInput.focus(); // Deprecated
}
}
DOM Manipulation Examples:
Measuring Elements:
function MeasurableComponent() {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const elementRef = useRef<HTMLDivElement>(null);
const measureElement = useCallback(() => {
if (elementRef.current) {
const rect = elementRef.current.getBoundingClientRect();
setDimensions({
width: rect.width,
height: rect.height
});
}
}, []);
useEffect(() => {
measureElement();
window.addEventListener('resize', measureElement);
return () => window.removeEventListener('resize', measureElement);
}, [measureElement]);
return (
<div>
<div
ref={elementRef}
style={{
width: '200px',
height: '100px',
background: 'lightblue',
margin: '20px'
}}
>
Measurable content
</div>
<p>Width: {dimensions.width}px, Height: {dimensions.height}px</p>
<button onClick={measureElement}>Re-measure</button>
</div>
);
}
Scroll Management:
function ScrollableList() {
const listRef = useRef<HTMLDivElement>(null);
const [items] = useState(Array.from({ length: 100 }, (_, i) => `Item ${i + 1}`));
const scrollToTop = () => {
listRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
};
const scrollToBottom = () => {
if (listRef.current) {
listRef.current.scrollTo({
top: listRef.current.scrollHeight,
behavior: 'smooth'
});
}
};
const scrollToItem = (index: number) => {
const itemElement = listRef.current?.children[index] as HTMLElement;
if (itemElement) {
itemElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
};
return (
<div>
<div style={{ marginBottom: '10px' }}>
<button onClick={scrollToTop}>Scroll to Top</button>
<button onClick={scrollToBottom}>Scroll to Bottom</button>
<button onClick={() => scrollToItem(50)}>Scroll to Item 50</button>
</div>
<div
ref={listRef}
style={{
height: '300px',
overflow: 'auto',
border: '1px solid #ccc',
padding: '10px'
}}
>
{items.map((item, index) => (
<div key={index} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
{item}
</div>
))}
</div>
</div>
);
}
Focus Management:
function FocusManager() {
const firstInputRef = useRef<HTMLInputElement>(null);
const secondInputRef = useRef<HTMLInputElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const handleKeyDown = (e: React.KeyboardEvent, nextRef: React.RefObject<HTMLElement>) => {
if (e.key === 'Enter' || e.key === 'Tab') {
e.preventDefault();
nextRef.current?.focus();
}
};
return (
<div>
<input
ref={firstInputRef}
placeholder="First input (press Enter to go to next)"
onKeyDown={(e) => handleKeyDown(e, secondInputRef)}
/>
<input
ref={secondInputRef}
placeholder="Second input (press Enter to go to button)"
onKeyDown={(e) => handleKeyDown(e, buttonRef)}
/>
<button
ref={buttonRef}
onClick={() => firstInputRef.current?.focus()}
>
Back to First Input
</button>
</div>
);
}
Custom Hook for DOM Measurements:
function useElementSize<T extends HTMLElement = HTMLDivElement>() {
const ref = useRef<T>(null);
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const element = ref.current;
if (!element) return;
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
setSize({ width, height });
}
});
resizeObserver.observe(element);
return () => {
resizeObserver.disconnect();
};
}, []);
return { ref, size };
}
// Usage
function ResizableComponent() {
const { ref, size } = useElementSize<HTMLDivElement>();
return (
<div>
<div
ref={ref}
style={{
width: '100%',
minHeight: '100px',
background: 'lightgreen',
padding: '20px'
}}
>
This element is {size.width}px wide and {size.height}px tall
</div>
</div>
);
}
Refs with Third-party Libraries:
function ChartComponent() {
const chartRef = useRef<HTMLCanvasElement>(null);
const chartInstance = useRef<Chart | null>(null);
useEffect(() => {
if (chartRef.current && !chartInstance.current) {
// Initialize chart library
chartInstance.current = new Chart(chartRef.current, {
type: 'line',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr'],
datasets: [{
label: 'Sales',
data: [12, 19, 3, 5],
borderColor: 'rgb(75, 192, 192)',
}]
}
});
}
return () => {
if (chartInstance.current) {
chartInstance.current.destroy();
chartInstance.current = null;
}
};
}, []);
const updateChart = (newData: number[]) => {
if (chartInstance.current) {
chartInstance.current.data.datasets[0].data = newData;
chartInstance.current.update();
}
};
return (
<div>
<canvas ref={chartRef} width={400} height={200} />
<button onClick={() => updateChart([1, 2, 3, 4])}>
Update Chart
</button>
</div>
);
}
Ref Best Practices:
- Use useRef for DOM access - Most common pattern
- Use callback refs for dynamic refs - When ref target changes
- Always check if ref.current exists - Use optional chaining
- Clean up third-party library instances - In useEffect cleanup
- Don't use refs for data flow - Use state and props instead
- Use forwardRef for component refs - When passing refs to child components
// forwardRef example
const FancyInput = forwardRef<HTMLInputElement, { placeholder: string }>(
({ placeholder }, ref) => {
return (
<input
ref={ref}
placeholder={placeholder}
style={{
border: '2px solid #007bff',
borderRadius: '4px',
padding: '8px'
}}
/>
);
}
);
// Usage
function ParentComponent() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<FancyInput ref={inputRef} placeholder="Fancy input" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}