Manche Aktionen lassen sich in React nicht deklarativ ausdrücken — sie sind ihrem Wesen nach imperativ: einem Input den Fokus geben, ein Element in den Viewport scrollen, die Größe eines DOM-Knotens messen, eine Text-Auswahl markieren. Für all das stellt React Refs bereit. Über useRef + die ref-Prop bekommt man Zugriff auf den DOM-Knoten und kann alle nativen DOM-APIs nutzen — focus(), scrollIntoView(), select(), getBoundingClientRect(). Wichtig: solche Operationen gehören in Event-Handler oder useEffect, nie in den Render-Body — dort ist der DOM-Knoten beim ersten Render noch nicht da.
Focus setzen
Klassischster Anwendungsfall: einem Input nach Mount oder nach einer Aktion den Focus geben.
import { useRef, useEffect } from 'react';
export default function SearchField() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Suchen…" />;
}Beim Mount läuft der Effect, das Input bekommt den Focus. Alternative für reine „autofocus beim Mount": das HTML-Attribut autoFocus. Für dynamisches Fokussieren (z.B. nach Validation-Fehler): Ref-Variante nötig.
Scroll in den Viewport
Wenn eine neue Nachricht in einem Chat hinzukommt oder eine Validation-Meldung erscheint, soll der User das Element automatisch sehen.
import { useRef, useEffect } from 'react';
export default function ChatMessage({ message, isLatest }) {
const messageRef = useRef(null);
useEffect(() => {
if (isLatest) {
messageRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [isLatest]);
return (
<div ref={messageRef} className="message">
{message.text}
</div>
);
}scrollIntoView ist eine native DOM-API. Mit behavior: 'smooth' animiert; mit block: 'center' wird das Element in der Mitte des Viewports positioniert.
Größe und Position messen
Für dynamische Layouts (Tooltip-Position, Resize-Logik) braucht man die echte Größe eines Elements.
import { useRef, useState, useLayoutEffect } from 'react';
export default function MeasureBox() {
const boxRef = useRef(null);
const [size, setSize] = useState({ w: 0, h: 0 });
useLayoutEffect(() => {
const rect = boxRef.current.getBoundingClientRect();
setSize({ w: rect.width, h: rect.height });
}, []);
return (
<>
<div ref={boxRef} style={{ padding: 20, border: '1px solid' }}>
Inhalt
</div>
<p>Gemessene Größe: {size.w} × {size.h}</p>
</>
);
}useLayoutEffect statt useEffect: die Messung läuft vor dem Browser-Paint, der State-Update damit verbunden ist auch sofort sichtbar — kein Flicker.
Bei Resize-Reaktion: ResizeObserver (Web-API) in useEffect registrieren, in der Callback setSize aufrufen.
Text-Auswahl markieren
input.select() markiert den gesamten Inhalt — praktisch bei „Code kopieren"-Buttons.
export default function CopyField({ value }) {
const inputRef = useRef(null);
const handleCopy = () => {
inputRef.current.select();
navigator.clipboard.writeText(value);
};
return (
<>
<input ref={inputRef} value={value} readOnly />
<button onClick={handleCopy}>Kopieren</button>
</>
);
}Externe Libraries einbinden
Charts, Drag-and-Drop, Maps, Editoren — viele JS-Libraries verlangen einen DOM-Knoten als Eingang. Refs sind die Brücke.
import { useRef, useEffect } from 'react';
import { createChart } from 'some-chart-library';
export default function PriceChart({ data }) {
const containerRef = useRef(null);
const chartRef = useRef(null);
useEffect(() => {
chartRef.current = createChart(containerRef.current, { data });
return () => {
chartRef.current.destroy();
};
}, []);
useEffect(() => {
chartRef.current?.setData(data);
}, [data]);
return <div ref={containerRef} style={{ height: 300 }} />;
}containerRef ist der Mount-Punkt, chartRef hält die Library-Instanz. Beim Unmount: destroy() für sauberen Cleanup.
Mehrere Refs in einer Liste
Wenn eine Liste gerendert wird und man pro Item eine Ref braucht (z.B. zu allen scrollIntoViewen): Map oder Array von Refs.
import { useRef } from 'react';
export default function CatalogList({ items }) {
const refsMap = useRef(new Map());
const scrollTo = (id) => {
refsMap.current.get(id)?.scrollIntoView({ behavior: 'smooth' });
};
return (
<>
<nav>
{items.map(item => (
<button key={item.id} onClick={() => scrollTo(item.id)}>
{item.title}
</button>
))}
</nav>
<ul>
{items.map(item => (
<li
key={item.id}
ref={(node) => {
if (node) refsMap.current.set(item.id, node);
else refsMap.current.delete(item.id);
}}
>
{item.body}
</li>
))}
</ul>
</>
);
}Das ist ein Callback Ref — eine Funktion als ref-Prop, die beim Mount/Unmount aufgerufen wird. Details im eigenen Artikel.
Häufige Stolperfallen
DOM-Manipulation in Render-Body — null-Fehler.
Beim ERSTEN Render existiert das DOM-Element noch nicht. ref.current.focus() im Body wirft TypeError. DOM-Operationen gehören in useEffect oder Event-Handler — nach dem Mount.
useEffect läuft nach Paint, `useLayoutEffect` davor.
Für DOM-Messungen, die State setzen, der die UI verändert: useLayoutEffect. Sonst sieht der User kurz das ungemessene Layout, dann den Sprung.
React-State und direkte DOM-Mutation nicht mischen.
ref.current.innerHTML = 'X' + im nächsten Render Komponente, die was anderes rendert: Bug. Wenn React rendern soll: State nutzen. DOM-Manipulation nur für Sachen, die React NICHT modelliert (focus, scroll, select).
ref kann `null` sein — Optional-Chaining benutzen.
Während Unmount oder bei conditional Rendering kann ref.current === null sein. ref.current?.focus() ist defensiv.
ResizeObserver/`IntersectionObserver` in `useEffect`, Cleanup nicht vergessen.
Klassisches Pattern: Observer beim Mount erstellen + observe(ref.current), beim Unmount disconnect(). Sonst Memory-Leak und Cross-Component-Trigger.
Externe Libraries in eigene useRef-Slot für die Instanz.
Wenn die Library eine Instanz zurückgibt (Chart, Map, Editor), in einer SEPARATEN ref speichern — nicht im DOM-ref. Bei Cleanup: instance.destroy() oder ähnliches aufrufen.
Lists mit pro-Item-Refs: Callback Ref mit Map.
Statt useRef pro Item (Hook-Regel-Verletzung in Schleife): EINEN useRef mit Map oder Array. Callback Ref fügt Items beim Mount hinzu, entfernt beim Unmount.
React-Tooltip-/Modal-Libraries machen das oft schon.
Für gängige Cases (Tooltip-Positionierung, Click-Outside-Detection, Focus-Trap) gibt's Headless-Libraries wie Floating UI, Radix UI, Headless UI. Eigene DOM-Manipulation oft unnötig.
Weiterführende Ressourcen
Externe Quellen
- Manipulating the DOM with Refs – react.dev
- Element.focus() – MDN
- Element.scrollIntoView() – MDN
- Element.getBoundingClientRect() – MDN
- ResizeObserver – MDN