navigation Navigation


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.

    Schematischer Aufbau
    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.

    Ändern des current Werts
    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.

    UseRefExampleOne.jsx
    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.

    React Hook - useRef - Beispiel mit Fokus-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.

    UseRefExampleTwo.jsx
    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.

    React Hook - useRef() - Vergleich mit useState und normaler Variable

    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.

    UseRefExampleThree.jsx
    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.

    React Hook - useRef - Ergebnis des Beispiels mit Timer

    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.

    UseRefExampleFour.jsx
    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.

    React Hook - useRef - Beispiel Chat - Ergebnis