useRef Hook'u: DOM Erişimi ve Kalıcı Değerler

useRef Hook'u: DOM Erişimi ve Kalıcı Değerler
React'in deklaratif yapısı çoğu durumda DOM ile doğrudan etkileşim ihtiyacını ortadan kaldırır. Ancak input'a odaklanma, scroll pozisyonu yönetimi veya üçüncü parti kütüphane entegrasyonu gibi durumlarda DOM erişimi kaçınılmazdır. useRef hook'u, DOM referansları tutmanın yanı sıra render'lar arasında kalıcı değerler saklamak için de kullanılır. Bu rehberde useRef'in tüm kullanım senaryolarını ve en iyi uygulamaları ele alacağız.
useRef'in Temeli
useRef, { current: initialValue } şeklinde bir ref objesi döndürür. Bu objenin iki temel özelliği vardır:
- Render'lar arasında değerini korur (useState gibi)
- Değeri değiştiğinde re-render tetiklemez (useState'ten farklı olarak)
import { useRef, useEffect, useState } from 'react';
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
// Bileşen mount olduğunda input'a odaklan
inputRef.current.focus();
}, []);
const handleClear = () => {
inputRef.current.value = '';
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Bir şey yazın..." />
<button onClick={handleClear}>Temizle</button>
</div>
);
}
DOM Erişim Senaryoları
1. Scroll Yönetimi
function ChatMessages({ messages }) {
const messagesEndRef = useRef(null);
const containerRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
// Yeni mesaj geldiğinde en alta kaydır
useEffect(() => {
scrollToBottom();
}, [messages]);
const scrollToTop = () => {
containerRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
};
return (
<div ref={containerRef} className="chat-container" style={{ overflowY: 'auto', maxHeight: '400px' }}>
{messages.map(msg => (
<div key={msg.id} className="message">
<strong>{msg.sender}:</strong> {msg.text}
</div>
))}
<div ref={messagesEndRef} />
<button onClick={scrollToTop} className="scroll-top-btn">Yukarı</button>
</div>
);
}
2. Element Boyutu Ölçme
function ResizablePanel() {
const panelRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
useEffect(() => {
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height
});
}
});
if (panelRef.current) {
observer.observe(panelRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={panelRef} className="resizable-panel">
<p>Boyut: {dimensions.width.toFixed(0)}px x {dimensions.height.toFixed(0)}px</p>
</div>
);
}
Kalıcı Değer Olarak useRef
useRef yalnızca DOM erişimi için değil, render'lar arasında re-render tetiklemeden değer saklamak için de kullanılır:
function StopWatch() {
const [elapsed, setElapsed] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null); // interval ID'si
const startTimeRef = useRef(null); // başlangıç zamanı
const start = () => {
if (isRunning) return;
setIsRunning(true);
startTimeRef.current = Date.now() - elapsed;
intervalRef.current = setInterval(() => {
setElapsed(Date.now() - startTimeRef.current);
}, 10);
};
const stop = () => {
setIsRunning(false);
clearInterval(intervalRef.current);
};
const reset = () => {
stop();
setElapsed(0);
};
// Cleanup
useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
const formatTime = (ms) => {
const minutes = Math.floor(ms / 60000);
const seconds = Math.floor((ms % 60000) / 1000);
const centiseconds = Math.floor((ms % 1000) / 10);
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(centiseconds).padStart(2, '0')}`;
};
return (
<div className="stopwatch">
<span className="time">{formatTime(elapsed)}</span>
<button onClick={start} disabled={isRunning}>Başlat</button>
<button onClick={stop} disabled={!isRunning}>Durdur</button>
<button onClick={reset}>Sıfırla</button>
</div>
);
}
İpucu: Bir değerin değişmesi UI'da güncelleme gerektirmiyorsa, useState yerine useRef kullanın. Bu gereksiz re-render'ları önler. Timer ID'leri, önceki değerler, flag'ler ve API controller'ları useRef için ideal kullanım alanlarıdır.
Önceki Değeri Takip Etme (usePrevious Pattern)
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
// Kullanım
function PriceTracker({ price }) {
const previousPrice = usePrevious(price);
const priceChange = previousPrice !== undefined
? price - previousPrice
: 0;
return (
<div>
<p>Güncel Fiyat: {price} TL</p>
{previousPrice !== undefined && (
<p style={{ color: priceChange > 0 ? 'green' : priceChange < 0 ? 'red' : 'gray' }}>
{priceChange > 0 ? '+' : ''}{priceChange.toFixed(2)} TL
</p>
)}
</div>
);
}
forwardRef ile Ref Aktarma
import { forwardRef, useRef, useImperativeHandle } from 'react';
// forwardRef ile ref'i alt bileşene aktarma
const CustomInput = forwardRef(function CustomInput({ label, ...props }, ref) {
return (
<div className="form-group">
<label>{label}</label>
<input ref={ref} {...props} />
</div>
);
});
// useImperativeHandle ile özel API sunma
const VideoPlayer = forwardRef(function VideoPlayer({ src }, ref) {
const videoRef = useRef(null);
useImperativeHandle(ref, () => ({
play: () => videoRef.current.play(),
pause: () => videoRef.current.pause(),
seekTo: (time) => { videoRef.current.currentTime = time; },
getDuration: () => videoRef.current.duration
}));
return <video ref={videoRef} src={src} />;
});
// Kullanım
function MediaPage() {
const playerRef = useRef(null);
return (
<div>
<VideoPlayer ref={playerRef} src="/video.mp4" />
<button onClick={() => playerRef.current.play()}>Oynat</button>
<button onClick={() => playerRef.current.pause()}>Duraklat</button>
<button onClick={() => playerRef.current.seekTo(0)}>Başa Sar</button>
</div>
);
}
Uyarı: Ref ile DOM'u doğrudan manipüle etmek (element ekleme/silme gibi) React'in kontrol ettiği DOM bölgelerinde çakışmalara neden olabilir. Ref'i yalnızca okuma amaçlı (focus, scroll, boyut ölçme) veya React'in yönetmediği DOM bölgelerinde kullanın.
Sonuç
useRef, React'te DOM erişimi ve render-bağımsız değer saklama için vazgeçilmez bir araçtır. DOM referansları, timer ID'leri, önceki değerlerin takibi ve üçüncü parti kütüphane entegrasyonları gibi birçok senaryoda kullanılır. forwardRef ve useImperativeHandle ile birlikte kullanıldığında, bileşenler arası DOM erişimini güvenli ve kontrollü bir şekilde sağlar. Ancak ref'i state yerine kullanma tuzağına düşmeyin; UI'da yansıması gereken değerler her zaman state olarak yönetilmelidir.