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.

TypeScript AutoFocus.jsx
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.

TypeScript ScrollIntoView.jsx
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.

TypeScript MeasureBox.jsx
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.

TypeScript CopyButton.jsx
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.

TypeScript ChartWrapper.jsx
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.

TypeScript ListMitRefs.jsx
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

/ Weiter

Zurück zu Refs

Zur Übersicht