useRef()
Der useRef Hook ist eine wichtige Funktionalität in React, die drei Hauptanwendungsbereiche bietet: direkte DOM-Manipulation, Speicherung von Werten zwischen Rendervorgängen und Referenzierung von Elementen im DOM. Anders als bei useState lösen Änderungen an einem ref-Objekt keine Neurendering der Komponente aus. Diese Eigenschaft macht useRef besonders nützlich für die Verwaltung von Werten, die sich ändern können, ohne dass ein Neurendering erforderlich ist. Als grundlegendes Tool im React-Ökosystem ermöglicht useRef eine effiziente Handhabung von Seiteneffekten und persistenten Werten in funktionalen Komponenten.
Inhaltsverzeichnis
Einführung
Was ist useRef()
in React? Hierbei handelt es sich um einen Hook, also eine Funktion, die hauptsächlich für zwei Aufgaben gut lösen kann.
- Direkten Zugriff auf HTML-Elemente ermöglichen (wie Input-Element oder ein Button-Element).
- Einen Wert speichern, der sich ändern kann, aber kein Re-Rendering des Components auslöst.
Man kann sich useRef()
wie eine Art Aufbewahrungsbox vorstellen, die eine Komponente über verschiedene Renderings hinweg behalten kann. Der Inhalt dieser Box kann sich ändern, aber das Ändern des Inhalts allein zwingt die Komponente nicht dazu, sich neu zu zeichnen (neu rendern).
Das ist auch der Haupt-Unterschied zu useState()
Hook. Wenn man einen State mit useState()
ändert, sagt React: “Okay, etwas Wichtiges hat sich geändert, ich muss die Komponente neu zeichnen, um die aktualisierten Werte anzuzeigen.” Bei useRef()
sagt React: “Ich habe die Änderung festgestellt. Der Wert in der Box hat sich geändert, aber das ist erstmal deine Sache, ich zeichen deswegen nichts neu.”
Warum braucht man useRef()
?
Wie schon oben kurz angedeutet, gibt es hauptsächlich zwei Situationen, in denen useRef()
nützlich ist.
1: Direkter Zugriff auf DOM-Elemente: Manchmal muss man direkt mit einem HTML-Element im Browser interagieren - zum Beispiel, um den Cursor in ein Eingabefeld zu setzen (Fokus), ein Video abzuspielen/pausieren oder die Größe eines Elements zu messen.
2: Speichern von veränderlichen Werten, die kein Re-Rendering auslösen: Man stelle sich vor, es gibt einen Wert, den man über die Zeit verfolgen oder ändern möchte (wie eine Timer-ID oder eine Zählervariable, deren Änderung nicht sofort die UI aktualisieren muss). Der Hook useRef()
ist perfekt dafür, weil es keinen unnötigen Re-Render-Vorgang auslöst.
Funktionsweise
1: Erstellen eines Ref-Objekts
Man erstellt ein Ref-Objekt mit der Funktion useRef()
. Der Rückgabewert dieser Funktion ist unser Ref-Objekt. Es besteht ebenfalls die Möglichkeit, einen Startwert an die Funktion als Parameter zu übergeben.
Der schematischer Aufbau sieht wie folgt aus.
const myRef = useRef(initialValue);
2: Die .current
Eigenschaft
Das Wichtigste an diesem myRef
Objekt ist seine .current
Eigenschaft. Diese Eigenschaft (myRef.current
) hält den eigentlichen Wert, welchen man speichern möchte. Man kann myRef.current
lesen und auch direkt ändern.
myRef.current = newValue;
Wenn man myRef.current
ändert, rendert React die Komponente nicht neu.
Beispiel - Fokus setzen
In diesem Beispiel verwenden wir useRef()
für eine einfache Aufgabe, nämlich um den Fokus auf ein Eingabefeld beim Auslösen eines Events (Klick auf einen Button) zu setzen.
import { useRef } from 'react';
function UseRefExampleOne() {
// Referenz-Objekt erstellen mit null als Startwert
const refFieldUsername = useRef(null);
// Event-Handler (Funktion) für den Button
const setFocus = () => {
if (refFieldUsername.current) {
try {
refFieldUsername.current.focus();
} catch (error) {
console.log(error);
}
}
};
return (
<div className="input-wrapper">
<input
type="text"
ref={refFieldUsername}
placeholder="Benutzername ..."
/>
<button onClick={setFocus}>
Fokus setzen
</button>
</div>
);
}
export default UseRefExampleOne;
Als Ergebnis erhalten wir im Browser zwei Elemente mit einem Eingabfeld und einem Button. Wenn wir auf den Button drauf klicken, wird die Funktion setFocus()
ausgeführt. In dieser Funktion prüfen wir, ob die Eigenschaft .current
am Objekt refFieldUsername
gesetzt ist. Wenn es der Fall ist, versuchen wir Fokus auf das Element, welches in .current
referenziert ist, zu setzen.
Beispiel - Vergleich mit useState
Im nächsten Beispiel werden wir auf eine einfache Art und Weise vergleichen, wie sich useRef()
von useState()
unterscheidet.
In diesem Beispiel werde ich zwei Helfer einsetzen, die nicht direkt mit der Funktionalität zusammenhängen.
1: useEffect Das setzen wir ohne Abhängigkeiten (auch keine leere) ein, um bei jedem Re-Rendering einen Log zu erhalten.
2: renderCount Wir definieren einen Zustandswert renderCount
. Diesen erhöhen wir unserer Funktion zum Auslösen eines Re-Renders für das Component. Mehr macht dieser Wert nicht.
import { useRef, useState, useEffect } from 'react';
function UseRefExampleTwo() {
// Helfer - Log beim Re-Render
useEffect(() => {
console.log('Component neu gerendert');
});
// Helfer - Trigger für Re-Render
const [renderCount, setRenderCount] = useState(0);
// State definieren - Startwert = 0
const [stateCounter, setStateCounter] = useState(0);
// Ref-Objekt definieren - Startwert = 0
const refCounter = useRef(0);
// Normale Variable definieren
let varCounter = 0;
const increaseStateCounter = () => {
setStateCounter(current => current + 1);
console.log('State counter erhöht - Component neu gerendert');
};
const increaseRefCounter = () => {
refCounter.current += 1;
console.log(`Ref coutner erhöht (${refCounter.current}) - Component NICHT neu gerendert`);
};
const increaseNormalVar = () => {
normalVar += 1;
console.log(`Normale Variable erhöht (${normalVar}) - Wird zurückgesetzt beim nächsten Rendering`);
};
const reRenderComponent = () => {
// Löst Re-Rendering aus
setRenderCount(prev => prev + 1);
};
return (
<div className="value-wrapper">
<div className="state-container">
<h3>useState</h3>
<p>Wert: {counter}</p>
<button onClick={increaseStateCounter}>
State +1
</button>
</div>
<hr />
<div className="ref-container">
<h3>useRef</h3>
<p>Wert: {refCounter.current}</p>
<button onClick={increaseRefCounter}>
Ref +1
</button>
</div>
<hr />
<div className="var-container">
<h3>Normale Variable</h3>
<p>Wert: {normalVar}</p>
<button onClick={increaseNormalVar}>
Variable +1
</button>
</div>
<div className="common-container">
<p>
<button onClick={reRenderComponent}>
Component neu rendern
</button>
</p>
</div>
</div>
);
}
export default UseRefExampleTwo;
Wenn wir also im Browser auf “State +1” klicken, sehen wir, dass das Component neu gerendert wird.
Klicken wir auf “Ref +1”, sehen wir in der Konsole, dass der Wert erhöht, aber das Component nicht neu gerendert wird. Die Aktualisierung des Wertes erfolgt beim nächsten Rendern. Entweder über direkte Auslösung des Re-Rendings oder über die Änderung einer State-Variable, wie des stateCounters
in diesem Beispiel.
Die normale Variable erhöht ebenfalls ihren Wert, was wir in der Konsole sehen können. Allerdings wird bei jedem Re-Rendering der Wert dieser Variable zurückgesetzt. Er wird nicht, wie beim Ref-Objekt (refCounter
) über das Re-Rendering hinweg beibehalten.
Beispiel - Stoppuhr
Dieses Beispiel demonstriert, wie nützlich useRef()
sein kann. Hier müssen wir die Timer-ID speichern, aber wir wollen nicht bei jeder Speicherung die ganze Komponente neu rendern.
import { useRef, useState } from 'react';
function UseRefExampleThree() {
// Zustand für die Zeit
// Wird angezeigt => Re-Rendering notwendig
const [currentTime, setCurrentTime] = useState(0);
const [isTimerRunning, setIsTimerRunning] = useState(false);
// Ref-Objekt für Timer-ID
const refTimer = useRef(null);
// Ref-Objekt für Startzeit
const refStartTime = useRef(0);
const startTimer = () => {
if (!isTimerRunning) {
setIsTimerRunning(true);
refStartTime.current = Date.now() - currentTime;
// Timer-ID in refTimer speichern
refTimer.current = setInterval(() => {
setCurrentTime(Date.now() - refStartTime.current);
}, 10);
}
};
const stopTimer = () => {
if (isTimerRunning) {
setIsTimerRunning(false);
clearInterval(refTimer.current);
refTimer.current = null;
}
};
const resetTimer = () => {
setIsTimerRunning(false);
setCurrentTime(0);
if (refTimer.current) {
clearInterval(refTimer.current);
refTimer.current = null;
}
};
// Aufräumarbeiten
// Wird nur beim Unmounten des Components ausgeführt
useEffect(() => {
return () => {
if (refTimer.current) {
clearInterval(refTimer.current);
}
};
}, []);
// Zeit formatieren
const formatTime = (milliseconds) => {
const totalSeconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const hundredPart = Math.floor((milliseconds & 1000) / 10);
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${hundredPart.toString().padStart(2, '0')}`;
};
return (
<div className="stop-timer">
<div className="stop-timer-heading">
<h3>Stoppuhr</h3>
</div>
<div
className="value-output"
style={{
padding: '10px',
borderRadius: '8px',
boxSizing: 'border-box',
backgroundColor: '#e8e8e8',
fontWeight: 'bold',
fontSize: '20px'
}}
>
{formatTime(currentTime)}
</div>
<hr />
<div
style={{
display: 'flex',
gap: '10px',
alignItems: 'center'
}}
>
<button onClick={startTimer} disabled={isTimerRunning}>
Start
</button>
<button onClick={stopTimer} disabled={!isTimerRunning}>
Stop
</button>
<button onClick={resetTimer}>
Reset
</button>
</div>
</div>
);
}
export function UseRefExampleThree;
Als Ergebnis haben wir eine Stoppuhr, welche wir starten, anhalten/fortsetzen und vollständig zurücksetzen können.
Beispiel - Chat
In diesem Beispiel werden wir useRef()
dafür verwenden, um direkt auf die DOM-Elemente zuzugreifen. Die Referenz-Objekte verwenden wir, um Fokus in ein Eingabefeld zu sezten, Informationen über ein Element abzurufen und zu einer bestimmten Position in einem referenzierten Container zu scrollen.
import { useRef, useState } from 'react';
function UseRefExampleFour() {
// Zustand für Nachrichten
const [message, setMessages] = useState([
'Willkommen im Chat',
'Das ist die erste Nachricht',
'Das ist die zweite Nachricht'
]);
// Zustand für neue Nachricht
const [newMessage, setNewMessage] = useState('');
// Zustand für Element-Information
const [elementInfo, setElementInfo] = useState('');
// Ref-Objekte für verschiedene Elemente
const refChatContainer = useRef(null);
const refChatInput = useRef(null);
const refMessagesContainer = useRef(null);
const addMessage = () => {
if (newMessage.trim()) {
setMessages(prev => [...prev, newMessage]);
setNewMessage('');
// Fokus auf Eingabe-Element setzen
refChatInput.current.focus();
// Nach Verzögerung nach unten scrollen
setTimeout(() => {
refChatContainer.current.scrollTop = refChatContainer.current.scrollHeight;
}, 100);
}
};
const jumpToEnd = () => {
refChatContainer.current.scrollTop = refChatContainer.current.scrollHeight;
};
const jumpToStart = () => {
refChatContainer.current.scrollTop = 0;
};
const showElementInfo = () => {
const infoContainer = refMessagesContainer.current;
const info = `
Höhe: ${infoContainer.offsetHeight}px
Breite: ${infoContainer.offsetWidth}px
Scroll-Höhe: ${infoContainer.scrollHeight}px
Anzahl Kinder: ${infoContainer.children.length}
`;
setElementInfo(info);
};
return (
<div className="chat-wrapper">
<h3
style={{
backgroundColor: 'lightblue',
padding: 20,
boxSizing: 'border-box',
borderRadius: 8
}}
>
Chat
</h3>
<hr />
<div
className="messages-container"
ref={refMessageContainer}
>
<div
className="chat-container"
ref={refChatContainer}
style={{
height: 48,
overflowY: 'auto',
backgroundColor: 'white',
borderRadius: 8,
marginBottom: '3rem',
border: '3px solid lightblue'
}}
>
{messages.map((message, index) => (
<div
key={index}
className="message-item"
>
{message}
</div>
))}
</div>
<div
className="chat-input-wrapper"
style={{
display: 'flex',
alignItems: 'center',
gap: 10
}}
>
<input
type="text"
value={newMessage}
ref={refChatInput}
onChange={(e) => setNewMessage(e.target.value)}
onKeyUp={(e) => e.key === 'Enter' && addMessage()}
placeholder="Neue Nachricht ..."
/>
<button onClick={addMessage}>
Senden
</button>
</div>
</div>
<hr />
<div
className="actions-wrapper"
style={{
display: 'flex',
gap: 10,
alignItems: 'center'
}}
>
<button onClick={jumpToTop}>
⬆️ Anfang
</button>
<button onClick={jumpToEnd}>
⬇️ Ende
</button>
<button onClick={showElementInfo}>
ℹ️ Info
</button>
</div>
<hr />
{elementInfo && (
<div className="element-info-wrapper">
<strong>Element-Info:</strong> <br/>
{elementInfo}
</div>
)}
</div>
);
}
export default UseRefExampleFour;
Als Ergebnis haben wir eine Mini-Chat-App, die eine Liste von Nachrichten zeigt. Wir können weitere, neue Nachrichten hinzufügen. Nach dem Hinzufügen wird im Nachrichten-Container immer nach unten gescrollt, sodass wir immer die aktuellste Nachricht sehen.