Fortgeschritten162025-01-15

React useRef - Refs und DOM Manipulation

Lerne useRef für DOM-Zugriff, uncontrolled Forms, Animations und persistente Werte die kein Re-Render auslösen!

#react#useRef#refs#dom#hooks

React useRef Hook

useRef gibt dir Zugriff auf DOM-Elemente und ermöglicht das Speichern von Werten die kein Re-Render auslösen!

Was ist useRef?

useRef Basics
import { useRef } from 'react'

function Component() {
  // Ref erstellen
  const inputRef = useRef(null)

  const focusInput = () => {
    // DOM Element direkt manipulieren
    inputRef.current.focus()
  }

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  )
}

// useRef gibt Object zurück: { current: value }
// .current = Der aktuelle Wert

useRef vs useState

Unterschied: useRef vs useState
import { useState, useRef } from 'react'

function Comparison() {
  // useState - Triggert Re-Render
  const [count1, setCount1] = useState(0)

  // useRef - Kein Re-Render!
  const count2 = useRef(0)

  const handleClick1 = () => {
    setCount1(count1 + 1)
    console.log('Render! count1:', count1)
  }

  const handleClick2 = () => {
    count2.current = count2.current + 1
    console.log('KEIN Render! count2:', count2.current)
  }

  console.log('Component rendered!')

  return (
    <div>
      <p>useState: {count1}</p>
      <button onClick={handleClick1}>Increment (Re-Render)</button>

      <p>useRef: {count2.current}</p>
      <button onClick={handleClick2}>Increment (Kein Re-Render)</button>
    </div>
  )
}

// useState: Wert ändert sich + Re-Render
// useRef: Wert ändert sich, KEIN Re-Render

DOM Element Zugriff

DOM Manipulation
import { useRef } from 'react'

function VideoPlayer() {
  const videoRef = useRef(null)

  const play = () => {
    videoRef.current.play()
  }

  const pause = () => {
    videoRef.current.pause()
  }

  const setVolume = (volume) => {
    videoRef.current.volume = volume
  }

  return (
    <div>
      <video ref={videoRef} src="video.mp4" />

      <button onClick={play}>Play</button>
      <button onClick={pause}>Pause</button>
      <button onClick={() => setVolume(0.5)}>50% Volume</button>
    </div>
  )
}

// Direkter Zugriff auf Video API! 🎥

Input Focus Management

Auto-Focus und Focus Management
import { useRef, useEffect } from 'react'

function SearchBar() {
  const inputRef = useRef(null)

  // Auto-focus beim Mount
  useEffect(() => {
    inputRef.current.focus()
  }, [])

  const clearAndFocus = () => {
    inputRef.current.value = ''
    inputRef.current.focus()
  }

  return (
    <div>
      <input
        ref={inputRef}
        type="text"
        placeholder="Search..."
      />
      <button onClick={clearAndFocus}>Clear</button>
    </div>
  )
}

// Multi-Step Form
function MultiStepForm() {
  const step1Ref = useRef(null)
  const step2Ref = useRef(null)
  const step3Ref = useRef(null)

  const [step, setStep] = useState(1)

  useEffect(() => {
    if (step === 1) step1Ref.current?.focus()
    if (step === 2) step2Ref.current?.focus()
    if (step === 3) step3Ref.current?.focus()
  }, [step])

  return (
    <form>
      {step === 1 && (
        <input ref={step1Ref} placeholder="Name" />
      )}
      {step === 2 && (
        <input ref={step2Ref} placeholder="Email" />
      )}
      {step === 3 && (
        <input ref={step3Ref} placeholder="Password" />
      )}

      <button onClick={() => setStep(step + 1)}>Next</button>
    </form>
  )
}

Uncontrolled Forms

Uncontrolled Components mit Refs
import { useRef } from 'react'

function UncontrolledForm() {
  const nameRef = useRef(null)
  const emailRef = useRef(null)
  const passwordRef = useRef(null)

  const handleSubmit = (e) => {
    e.preventDefault()

    const formData = {
      name: nameRef.current.value,
      email: emailRef.current.value,
      password: passwordRef.current.value
    }

    console.log(formData)
    // API Call...
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} placeholder="Name" />
      <input ref={emailRef} type="email" placeholder="Email" />
      <input ref={passwordRef} type="password" placeholder="Password" />

      <button type="submit">Submit</button>
    </form>
  )
}

// Kein useState! Kein onChange! Nur Refs! ⚡
// Gut für große Forms mit vielen Inputs

Previous Value speichern

Previous State mit useRef
import { useRef, useEffect } from 'react'

function usePrevious(value) {
  const ref = useRef()

  useEffect(() => {
    ref.current = value
  }, [value])

  return ref.current
}

// Verwenden
function Counter() {
  const [count, setCount] = useState(0)
  const prevCount = usePrevious(count)

  return (
    <div>
      <h1>Current: {count}</h1>
      <h2>Previous: {prevCount}</h2>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  )
}

// Animation Direction
function Score({ score }) {
  const prevScore = usePrevious(score)
  const isIncreasing = score > prevScore

  return (
    <div className={isIncreasing ? 'increase-animation' : 'decrease-animation'}>
      Score: {score}
    </div>
  )
}

Render Count

Render Count mit useRef
import { useRef, useEffect } from 'react'

function Component() {
  const renderCount = useRef(0)

  useEffect(() => {
    renderCount.current = renderCount.current + 1
  })

  return (
    <div>
      <h1>Renders: {renderCount.current}</h1>
    </div>
  )
}

// Custom Hook
function useRenderCount() {
  const renderCount = useRef(0)

  useEffect(() => {
    renderCount.current += 1
  })

  return renderCount.current
}

// Verwenden
function MyComponent() {
  const [count, setCount] = useState(0)
  const renders = useRenderCount()

  return (
    <div>
      <p>Count: {count}</p>
      <p>Component rendered {renders} times</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

Scroll to Element

Scroll Behavior
import { useRef } from 'react'

function ScrollToSection() {
  const section1Ref = useRef(null)
  const section2Ref = useRef(null)
  const section3Ref = useRef(null)

  const scrollToSection = (ref) => {
    ref.current.scrollIntoView({ behavior: 'smooth' })
  }

  return (
    <div>
      <nav>
        <button onClick={() => scrollToSection(section1Ref)}>
          Section 1
        </button>
        <button onClick={() => scrollToSection(section2Ref)}>
          Section 2
        </button>
        <button onClick={() => scrollToSection(section3Ref)}>
          Section 3
        </button>
      </nav>

      <section ref={section1Ref}>
        <h2>Section 1</h2>
        <p>Content...</p>
      </section>

      <section ref={section2Ref}>
        <h2>Section 2</h2>
        <p>Content...</p>
      </section>

      <section ref={section3Ref}>
        <h2>Section 3</h2>
        <p>Content...</p>
      </section>
    </div>
  )
}

// Scroll to Top
function ScrollToTop() {
  const topRef = useRef(null)

  return (
    <div>
      <div ref={topRef} />
      {/* Long content */}
      <button onClick={() => topRef.current.scrollIntoView({ behavior: 'smooth' })}>
        ↑ Scroll to Top
      </button>
    </div>
  )
}

Interval/Timeout mit useRef

Cleanup für Timer
import { useRef, useEffect } from 'react'

function Timer() {
  const [seconds, setSeconds] = useState(0)
  const intervalRef = useRef(null)

  const startTimer = () => {
    if (intervalRef.current) return // Already running

    intervalRef.current = setInterval(() => {
      setSeconds(s => s + 1)
    }, 1000)
  }

  const stopTimer = () => {
    clearInterval(intervalRef.current)
    intervalRef.current = null
  }

  const resetTimer = () => {
    stopTimer()
    setSeconds(0)
  }

  // Cleanup beim Unmount
  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current)
      }
    }
  }, [])

  return (
    <div>
      <h1>Time: {seconds}s</h1>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
      <button onClick={resetTimer}>Reset</button>
    </div>
  )
}

// Debounce mit useRef
function SearchWithDebounce() {
  const [query, setQuery] = useState('')
  const timeoutRef = useRef(null)

  const handleSearch = (value) => {
    setQuery(value)

    // Clear existing timeout
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current)
    }

    // Set new timeout
    timeoutRef.current = setTimeout(() => {
      console.log('Searching for:', value)
      // API Call
    }, 500)
  }

  return (
    <input
      type="text"
      value={query}
      onChange={(e) => handleSearch(e.target.value)}
      placeholder="Search..."
    />
  )
}

Measuring Elements

Element Größe messen
import { useRef, useState, useEffect } from 'react'

function MeasureElement() {
  const boxRef = useRef(null)
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 })

  useEffect(() => {
    const updateDimensions = () => {
      if (boxRef.current) {
        setDimensions({
          width: boxRef.current.offsetWidth,
          height: boxRef.current.offsetHeight
        })
      }
    }

    updateDimensions()
    window.addEventListener('resize', updateDimensions)

    return () => window.removeEventListener('resize', updateDimensions)
  }, [])

  return (
    <div>
      <div
        ref={boxRef}
        style={{
          width: '50%',
          height: '200px',
          background: 'blue'
        }}
      >
        Resize window!
      </div>
      <p>Width: {dimensions.width}px</p>
      <p>Height: {dimensions.height}px</p>
    </div>
  )
}

// getBoundingClientRect
function ElementPosition() {
  const elementRef = useRef(null)
  const [position, setPosition] = useState({ top: 0, left: 0 })

  const updatePosition = () => {
    const rect = elementRef.current.getBoundingClientRect()
    setPosition({
      top: rect.top,
      left: rect.left
    })
  }

  return (
    <div>
      <div ref={elementRef}>Element</div>
      <button onClick={updatePosition}>Get Position</button>
      <p>Top: {position.top}px</p>
      <p>Left: {position.left}px</p>
    </div>
  )
}

Canvas Animation

Canvas mit useRef
import { useRef, useEffect } from 'react'

function CanvasAnimation() {
  const canvasRef = useRef(null)
  const animationRef = useRef(null)

  useEffect(() => {
    const canvas = canvasRef.current
    const ctx = canvas.getContext('2d')
    let x = 0

    const animate = () => {
      ctx.clearRect(0, 0, canvas.width, canvas.height)

      // Draw circle
      ctx.beginPath()
      ctx.arc(x, 100, 20, 0, Math.PI * 2)
      ctx.fillStyle = 'red'
      ctx.fill()

      x = (x + 2) % canvas.width
      animationRef.current = requestAnimationFrame(animate)
    }

    animate()

    return () => {
      cancelAnimationFrame(animationRef.current)
    }
  }, [])

  return (
    <canvas
      ref={canvasRef}
      width={400}
      height={200}
      style={{ border: '1px solid black' }}
    />
  )
}

forwardRef - Refs weitergeben

Refs an Child Components
import { forwardRef, useRef } from 'react'

// Custom Input Component mit forwardRef
const CustomInput = forwardRef((props, ref) => {
  return (
    <input
      ref={ref}
      {...props}
      style={{
        padding: '10px',
        border: '2px solid blue',
        borderRadius: '5px'
      }}
    />
  )
})

// Parent Component
function Form() {
  const inputRef = useRef(null)

  const focusInput = () => {
    inputRef.current.focus()
  }

  return (
    <div>
      <CustomInput ref={inputRef} placeholder="Enter text" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  )
}

// Ohne forwardRef würde ref nicht funktionieren!

// Mit mehreren Refs
const FancyInput = forwardRef((props, ref) => {
  const localRef = useRef(null)

  const handleClick = () => {
    localRef.current.style.background = 'yellow'
  }

  return (
    <div>
      <input ref={ref} {...props} />
      <button ref={localRef} onClick={handleClick}>
        Style Me
      </button>
    </div>
  )
})

📝 Quiz

Wann solltest du useRef verwenden?

Tipps & Tricks

Ref vs State Decision

// Brauchst du Re-Render wenn Wert sich ändert?
const [value, setValue] = useState(0) // ✅ Ja -> useState

// Brauchst du KEIN Re-Render?
const value = useRef(0) // ✅ Nein -> useRef

// Examples:
const [username, setUsername] = useState('') // UI zeigt an -> useState
const renderCount = useRef(0) // Nur fürs Tracking -> useRef
const timerId = useRef(null) // Für Cleanup -> useRef

Multiple Refs

function TodoList({ todos }) {
  // Array von Refs
  const todoRefs = useRef([])

  const focusTodo = (index) => {
    todoRefs.current[index]?.focus()
  }

  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={todo.id}>
          <input
            ref={(el) => todoRefs.current[index] = el}
            defaultValue={todo.text}
          />
        </li>
      ))}
    </ul>
  )
}

Ref Callback Pattern

function Component() {
  const [height, setHeight] = useState(0)

  // Callback Ref statt useRef
  const measureRef = (node) => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height)
    }
  }

  return <div ref={measureRef}>Content</div>
}

useRef für Latest Value

function useLatestValue(value) {
  const ref = useRef(value)

  useEffect(() => {
    ref.current = value
  })

  return ref
}

// Nützlich für Event Handlers
function Component({ onChange }) {
  const onChangeRef = useLatestValue(onChange)

  useEffect(() => {
    const handler = () => onChangeRef.current()
    window.addEventListener('change', handler)
    return () => window.removeEventListener('change', handler)
  }, []) // Empty deps, aber nutzt aktuelles onChange
}

Häufige Fehler

Fehler 1: Ref während Render lesen

FALSCH:

function Component() {
  const ref = useRef(0)

  return <div>{ref.current}</div>
  // Ändert sich nicht bei Updates!
}

RICHTIG:

function Component() {
  const [count, setCount] = useState(0)

  return <div>{count}</div>
  // State für UI-Werte!
}

Fehler 2: Ref direkt ändern und erwarten Re-Render

FALSCH:

function Component() {
  const count = useRef(0)

  const increment = () => {
    count.current += 1
    // Component wird NICHT re-rendern!
  }

  return <div>{count.current}</div>
}

RICHTIG:

function Component() {
  const [count, setCount] = useState(0)

  const increment = () => {
    setCount(count + 1)
    // Re-Render!
  }

  return <div>{count}</div>
}

Fehler 3: Ref ohne .current

FALSCH:

const inputRef = useRef(null)
inputRef.focus() // Error!

RICHTIG:

const inputRef = useRef(null)
inputRef.current.focus() // ✅

Fehler 4: Cleanup vergessen

FALSCH:

function Component() {
  const intervalRef = useRef(null)

  const start = () => {
    intervalRef.current = setInterval(() => {
      console.log('tick')
    }, 1000)
  }

  // Kein cleanup! Memory Leak! 💥
}

RICHTIG:

function Component() {
  const intervalRef = useRef(null)

  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current)
      }
    }
  }, [])
}
🎯

Zusammenfassung

Du hast gelernt:

  • ✅ useRef für DOM-Zugriff
  • ✅ useRef vs useState (Kein Re-Render!)
  • ✅ Input Focus Management
  • ✅ Uncontrolled Forms mit Refs
  • ✅ Previous Values speichern
  • ✅ Render Count tracking
  • ✅ Scroll Behavior
  • ✅ Timer/Interval Refs
  • ✅ Canvas Animation
  • ✅ forwardRef für Custom Components

Key Takeaways:

  • useRef = { current: value }
  • Kein Re-Render bei Änderung
  • Perfekt für DOM-Zugriff
  • Gut für Timer IDs
  • Previous Values speichern
  • .current nicht vergessen!
  • Cleanup wichtig

Wann useRef:

  • ✅ DOM Element Zugriff
  • ✅ Focus Management
  • ✅ Uncontrolled Forms
  • ✅ Timer/Interval IDs
  • ✅ Previous Values
  • ✅ Render Count
  • ✅ Canvas/Animation
  • ❌ UI State (useState nutzen!)

useRef vs useState:

  • useRef: Kein Re-Render, für Non-Visual State
  • useState: Re-Render, für Visual State

Praktische Übungen

Übung 1: Auto-Focus Form

Erstelle ein Form mit:

  • 3 Input Feldern
  • Auto-Focus beim ersten Input
  • Enter drücken -> Nächstes Input
  • Refs für alle Inputs

Übung 2: Video Player

Baue einen Video Player:

  • Play/Pause Buttons
  • Volume Control
  • Current Time anzeigen
  • useRef für video element

Übung 3: Click Outside

Implementiere:

  • Dropdown Menu
  • Schließt bei Klick außerhalb
  • useRef + useEffect
  • Event Listener Cleanup

Übung 4: Stopwatch

Erstelle eine Stoppuhr:

  • Start/Stop/Reset
  • useRef für Interval
  • Zeit anzeigen
  • Proper Cleanup

Übung 5: Scroll Spy

Baue Scroll Spy Navigation:

  • Multiple Sections
  • Active Link Highlighting
  • Smooth Scroll
  • useRef für Section Refs
ReactLektion 10 von 10
100% abgeschlossen
Lektion abgeschlossen!

Gut gemacht! 🎉

Du hast "React useRef - Refs und DOM Manipulation" abgeschlossen

Artikel bewerten

0.0 (0 Bewertungen)

Bitte einloggen um zu bewerten