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?
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 WertuseRef 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-RenderDOM Element Zugriff
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
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
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 InputsPrevious Value speichern
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
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
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
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
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
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
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
Gut gemacht! 🎉
Du hast "React useRef - Refs und DOM Manipulation" abgeschlossen
Artikel bewerten
Bitte einloggen um zu bewerten
Das könnte dich auch interessieren
React Context API - Globaler State ohne Props Drilling
Lerne die React Context API kennen und vermeide Props Drilling. Teile State einfach zwischen Komponenten ohne Redux!
React Custom Hooks - Wiederverwendbare Logik
Lerne Custom Hooks zu erstellen und Code zwischen Komponenten zu teilen. DRY-Prinzip in React mit eigenen Hooks!
React useEffect Hook - Side Effects & Lifecycle
Lerne den useEffect Hook für Side Effects, Data Fetching, Subscriptions und Component Lifecycle in React Function Components.