Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Zur Übersicht
Kern 10 Min Lesezeit

useRef

Etwas merken, ohne dass React davon aufwacht

Baut auf: useState
In einem Satz

useRef ist eine kleine Kiste, die React zwischen den Durchläufen aufhebt. Etwas hineinzulegen löst kein Neuzeichnen aus – genau dafür ist sie da.

aufbau.ts
const kiste = useRef(startwert);

kiste.current            // hineinschauen
kiste.current = neu;     // hineinlegen  ->  React zeichnet NICHT neu
Bildlich gesprochen

useState ist eine Anzeigetafel: Schreibst du darauf, schauen alle hin. useRef ist ein Zettel in der Hosentasche: Er bleibt dir erhalten, aber niemand merkt, wenn du ihn änderst.

Zweck 1: ein Element auf der Seite anfassen

Manchmal musst du dem Browser direkt etwas sagen: „setz den Cursor hierhin“, „scroll dorthin“, „spiel das Video ab“. Dafür brauchst du das echte Element – und genau das legt React für dich in die Kiste.

dom.tsx
function Suche() {
  const inputRef = useRef<HTMLInputElement>(null);

  return (
    <>
      <input ref={inputRef} />
      <button onClick={() => inputRef.current?.focus()}>Fokus</button>
    </>
  );
}
Das ref-Attribut sagt React: „leg mir dieses Element in die Kiste, sobald es auf der Seite ist“.
Gut zu wissen Deshalb steht am Anfang null drin

Beim ersten Durchlauf existiert das Element noch gar nicht – React baut es ja erst danach. Deshalb greifst du mit ?. zu oder erst in einem Klick-Handler.

Zweck 2: still etwas merken

merken.tsx
// Die Nummer eines Timers, um ihn später abbrechen zu können
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

function beiEingabe() {
  if (timerRef.current) clearTimeout(timerRef.current);
  timerRef.current = setTimeout(suchen, 500);
}

// Wie oft wurde gezeichnet? (nur zum Anschauen, nicht zum Anzeigen)
const zaehler = useRef(0);
zaehler.current += 1;

State oder Ref? Eine einzige Frage

Muss sich das Bild ändern?
Ref – die Anzeige bleibt bei 0 stehen
const countRef = useRef(0);

function klick() {
  countRef.current += 1;
}

return <p>{countRef.current}</p>;
State – die Zahl ist sichtbar
const [count, setCount] = useState(0);

function klick() {
  setCount((c) => c + 1);
}

return <p>{count}</p>;

Links steigt die Zahl in der Kiste tatsächlich – nur zeichnet React nicht neu, also siehst du immer die 0. Sichtbar → State. Unsichtbar → Ref.

Nimm es, wenn …
  • +

    Ein Element anfassen: focus(), scrollen, messen, Video steuern

  • +

    Timer-Nummern aufbewahren

  • +

    Den vorherigen Wert von etwas merken

  • +

    Objekte fremder Bibliotheken halten (Karte, Diagramm, Editor)

Lass es, wenn …
  • Werte, die auf dem Bildschirm erscheinen – das ist State

  • Als Trick, um Neuzeichnen zu „sparen“ – dann friert die Anzeige ein

  • Lesen oder Schreiben mitten im Zeichnen

Achtung Nicht während des Zeichnens anfassen

Lies und schreib Refs in Klick-Handlern oder in useEffect – nicht direkt im Funktionskörper. Sonst hängt das Ergebnis davon ab, wann React zufällig zeichnet, und das kann React nicht garantieren.

Genauer erklärt: Callback-Refs und ref als Prop optional

Statt einer Kiste kannst du dem ref-Attribut auch eine Funktion geben. React ruft sie auf, sobald das Element auf die Seite kommt – und noch einmal, wenn es verschwindet. Praktisch, wenn du direkt danach etwas messen willst.

callback-ref.tsx
function Box() {
  const [hoehe, setHoehe] = useState(0);

  return (
    <div
      ref={(node) => {
        if (node) setHoehe(node.getBoundingClientRect().height);
        // React 19: was du hier zurückgibst, dient als Aufräumfunktion
      }}
    >

    </div>
  );
}
Gut zu wissen React 19: kein forwardRef mehr nötig

ref ist inzwischen eine ganz normale Prop: function Input({ ref, ...rest }) { … }. In älterem Code siehst du dafür noch forwardRef – das funktioniert weiterhin, ist aber nicht mehr nötig.

Sitzt das schon?

4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.

Jetzt selbst schreiben

Fokus setzen und still mitzählen

Zwei Aufgaben: Der Button soll den Cursor ins Eingabefeld setzen, und daneben soll mitgezählt werden, wie oft die Komponente gezeichnet wurde – ohne dass das Zählen selbst ein Zeichnen auslöst.

  • Klick auf Fokus setzt den Cursor ins Feld
  • Die Zahl bei Renders steigt beim Tippen
  • Das Mitzählen selbst löst kein zusätzliches Zeichnen aus
import { useRef, useState } from "react";

export default function App() {
  const [text, setText] = useState("");
  // TODO: Ref für das Eingabefeld
  // TODO: Ref für den Zähler

  return (
    <div style={{ fontFamily: "system-ui", padding: 16 }}>
      <input aria-label="feld" value={text} onChange={(e) => setText(e.target.value)} />{" "}
      <button onClick={() => { /* TODO: fokussieren */ }}>Fokus</button>
      <p data-testid="renders">Renders: 0</p>
    </div>
  );
}

Hirefullstack

Ihr braucht React-Verstärkung im Team?

Wir bauen seit Jahren React- und Next.js-Anwendungen für Kunden in ganz Deutschland – als einzelner Experte, als Verstärkung fürs Bestandsteam oder als komplettes Scrum-Team.

Projekt besprechen →