useRef
useState und useReducer verwalten Werte, deren Änderungen die Komponente neu rendern sollen. Manchmal braucht man aber einen Wert, der über Renders hinweg erhalten bleibt — ohne dass eine Änderung ein Re-Render auslöst. Und manchmal braucht man direkten Zugriff auf ein DOM-Element. Beides löst useRef.
Ein Ref ist eine Box
Abschnitt betitelt „Ein Ref ist eine Box“useRef gibt ein Objekt mit einem einzigen Feld zurück: .current. Dieses Objekt bleibt über alle Renders hinweg dieselbe Referenz — React ersetzt es nie. Man kann .current jederzeit lesen und schreiben, ohne dass React davon erfährt.
import { useRef } from 'react'
const ref = useRef(0)ref.current = ref.current + 1 // kein Re-Renderconsole.log(ref.current) // aktueller WertDas ist der fundamentale Unterschied zu useState: State-Änderungen lösen einen Re-Render aus und der neue Wert erscheint beim nächsten Render. Ref-Änderungen lösen keinen Re-Render aus — der Wert ist sofort da, aber React weiß nichts davon.
Use Case 1: Mutabler Wert ohne Re-Render
Abschnitt betitelt „Use Case 1: Mutabler Wert ohne Re-Render“Interval-ID speichern
Abschnitt betitelt „Interval-ID speichern“Ein häufiger Fall: eine setInterval-ID muss gespeichert werden, damit das Interval später gestoppt werden kann. Sie gehört nicht in State — ihre Änderung soll kein Re-Render auslösen:
import { useRef, useState } from 'react'
export default function Stoppuhr() { const [sekunden, setSekunden] = useState(0) const intervalRef = useRef<number | null>(null)
function starten() { if (intervalRef.current !== null) return intervalRef.current = setInterval(() => { setSekunden(s => s + 1) }, 1000) }
function stoppen() { clearInterval(intervalRef.current!) intervalRef.current = null }
return ( <div> <p>{sekunden} Sekunden</p> <button onClick={starten}>Start</button> <button onClick={stoppen}>Stop</button> </div> )}sekunden gehört in State — die Anzeige soll sich aktualisieren. intervalRef gehört in einen Ref — die ID ist nur für clearInterval nötig, ihre Änderung soll nichts rendern.
Vorherigen Wert merken
Abschnitt betitelt „Vorherigen Wert merken“Mit einem Ref lässt sich der Wert aus dem letzten Render festhalten:
import { useRef, useEffect, useState } from 'react'
function usePrevious<T>(value: T): T | undefined { const ref = useRef<T | undefined>(undefined)
useEffect(() => { ref.current = value })
return ref.current}
export default function TodoZaehler({ anzahl }: { anzahl: number }) { const vorige = usePrevious(anzahl)
return ( <p> {anzahl} Todos {vorige !== undefined && vorige !== anzahl && ( <span> (vorher: {vorige})</span> )} </p> )}Der Effect läuft nach jedem Render — er schreibt den aktuellen Wert in den Ref, nachdem die Komponente gerendert hat. Beim nächsten Render liest usePrevious den Ref noch vor dem Effect — also den Wert vom letzten Render.
Use Case 2: DOM-Ref
Abschnitt betitelt „Use Case 2: DOM-Ref“Manchmal muss man direkt mit einem DOM-Element interagieren: ein Eingabefeld fokussieren, ein Video starten, die Scrollposition lesen, eine nicht-React-Bibliothek anbinden. React bietet dafür das ref-Prop.
Fokus setzen
Abschnitt betitelt „Fokus setzen“import { useRef } from 'react'
export default function SuchFormular() { const inputRef = useRef<HTMLInputElement>(null)
function handleSuchen() { inputRef.current?.focus() }
return ( <div> <input ref={inputRef} placeholder="Suche..." /> <button onClick={handleSuchen}>Fokus</button> </div> )}Wenn React das <input> in den DOM einfügt, schreibt es das echte DOM-Element in inputRef.current. Wenn die Komponente unmountet, setzt React inputRef.current wieder auf null.
Nach dem Hinzufügen zu einem Todo scrollen
Abschnitt betitelt „Nach dem Hinzufügen zu einem Todo scrollen“import { useRef, useState } from 'react'
export default function TodoListe() { const [todos, setTodos] = useState<string[]>([]) const listeRef = useRef<HTMLUListElement>(null)
function hinzufuegen(title: string) { setTodos(prev => [...prev, title]) setTimeout(() => { listeRef.current?.scrollTo({ top: listeRef.current.scrollHeight, behavior: 'smooth', }) }, 0) }
return ( <ul ref={listeRef} style={{ maxHeight: 200, overflowY: 'auto' }}> {todos.map((t, i) => <li key={i}>{t}</li>)} </ul> )}Das setTimeout(..., 0) stellt sicher, dass erst das DOM aktualisiert wird — dann erst wird gescrollt. Sauberer ist useEffect mit todos als Abhängigkeit — der Effect läuft garantiert nach dem DOM-Update:
import { useRef, useState, useEffect } from 'react'
export default function TodoListe() { const [todos, setTodos] = useState<string[]>([]) const listeRef = useRef<HTMLUListElement>(null)
useEffect(() => { listeRef.current?.scrollTo({ top: listeRef.current.scrollHeight, behavior: 'smooth', }) }, [todos])
function hinzufuegen(title: string) { setTodos(prev => [...prev, title]) }
return ( <ul ref={listeRef} style={{ maxHeight: 200, overflowY: 'auto' }}> {todos.map((t, i) => <li key={i}>{t}</li>)} </ul> )}useEffect(() => { ... }, [todos]) läuft jedes Mal, wenn sich todos ändert — immer nach dem Render, wenn das DOM bereits aktualisiert ist. Das [todos] ist das Dependency Array: es bestimmt, wann der Effect neu ausgeführt wird. Die vollständige Erklärung folgt im Kapitel useEffect: Daten laden.
TypeScript
Abschnitt betitelt „TypeScript“Mutabler Wert: Der Typ des Initialwerts bestimmt ref.current:
const intervalRef = useRef<number | null>(null)// ref.current: number | null
const zaehlerRef = useRef(0)// ref.current: number — inferiert aus dem InitialwertDOM-Ref: Der Generic gibt den erwarteten DOM-Elementtyp an, der Initialwert ist null:
const inputRef = useRef<HTMLInputElement>(null)// ref.current: HTMLInputElement | null
const formRef = useRef<HTMLFormElement>(null)const divRef = useRef<HTMLDivElement>(null)ref.current ist null, bis React das Element in den DOM eingehängt hat — deshalb der optionale Chaining-Operator (?.) beim Zugriff.
useState vs. useRef
Abschnitt betitelt „useState vs. useRef“useState | useRef | |
|---|---|---|
| Löst Re-Render aus | ja | nein |
| Wert sichtbar beim Render | ja | nein — nur via .current |
| Geeignet für | Daten, die die UI steuern | IDs, DOM-Elemente, Hilfswerte |
| Wert nach Render aktuell | ja | ja — sofort |
Faustregel: Wenn die Änderung eines Werts die Anzeige verändern soll, gehört er in State. Wenn der Wert nur intern gebraucht wird — als Hilfsgröße, ID oder DOM-Handle — gehört er in einen Ref.