28.01.2026

React’te Stabil Referanslar: useRef ile “Latest Value” Deseni ve Stale Closure Sorununu Bitirmek

Stale closure kaynaklı hataları, useRef tabanlı “latest value” deseniyle güvenli ve okunabilir şekilde çöz.

React’te bazen her şey doğru görünür ama bir interval, event listener ya da async callback içinde “eski state” ile çalışan gizemli bug’lar çıkar. Bunun adı genelde stale closure: Fonksiyon, oluşturulduğu render anındaki değerleri “hatırlar”.

Bu yazıda useRef ile stabil referans + güncel değer yaklaşımını ("latest value" deseni) kurup bu sınıf hataları sistematik şekilde çözeceğiz.


Problem: Interval içindeki state neden güncellenmiyor?

import { useEffect, useState } from "react";

export function CounterBad() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      // Beklenti: sürekli artsın
      // Gerçek: bazen 1'de takılır veya beklenmedik davranır
      setCount(count + 1);
    }, 1000);

    return () => clearInterval(id);
  }, []); // count burada yok

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

useEffect boş dependency ile sadece bir kez çalışır; içerideki callback de o ilk render’daki count değerini kapatır.

“Hızlı fix”: Functional update

Bu örnekte en iyi çözüm aslında:

setCount(c => c + 1);

Ama her problem functional update ile çözülmez. Mesela interval içinde yalnızca sayacı değil, en güncel filtreyi, token’ı, seçilmiş kullanıcıyı okumak isteyebilirsiniz.


Çözüm: Latest Value (useRef) deseni

Amaç: Callback stabil kalsın (listener/interval yeniden kurulmasın) ama okuduğu değer her zaman güncel olsun.

1) useLatest hook’u

import { useEffect, useRef } from "react";

export function useLatest(value) {
  const ref = useRef(value);

  // render sonrası güncelle
  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref;
}

Not: Bazı senaryolarda useLayoutEffect tercih edilebilir; çoğu UI durumunda useEffect yeterlidir.


Örnek: Scroll event’inde en güncel ayarları okumak

Diyelim ki kullanıcı bir ayardan “yukarı çıkınca header’ı gizle” davranışını açıp kapatabiliyor. Scroll listener’ı her toggle’da kaldırıp eklemek istemiyoruz.

import { useEffect, useState } from "react";
import { useLatest } from "./useLatest";

export function Header() {
  const [hideOnScrollUp, setHideOnScrollUp] = useState(true);
  const [hidden, setHidden] = useState(false);

  const latestHideOnScrollUp = useLatest(hideOnScrollUp);

  useEffect(() => {
    let lastY = window.scrollY;

    function onScroll() {
      const y = window.scrollY;
      const goingUp = y < lastY;

      if (latestHideOnScrollUp.current && goingUp) {
        setHidden(true);
      } else {
        setHidden(false);
      }

      lastY = y;
    }

    window.addEventListener("scroll", onScroll, { passive: true });
    return () => window.removeEventListener("scroll", onScroll);
  }, [latestHideOnScrollUp]);

  return (
    <header style={{ transform: hidden ? "translateY(-100%)" : "translateY(0)", transition: "200ms" }}>
      <label>
        <input
          type="checkbox"
          checked={hideOnScrollUp}
          onChange={(e) => setHideOnScrollUp(e.target.checked)}
        />
        Yukarı kaydırınca gizle
      </label>
    </header>
  );
}

Bu pattern ile:

  • Listener tek kez eklenir.
  • İçerideki mantık daima güncel hideOnScrollUp değerini okur.
  • Dependency listesi sade kalır.

Bonus: Async callback’lerde güncel state (örn. “en son seçili ürün”)

Bir setTimeout veya ağ isteği dönüşünde, kullanıcı bu sırada seçim değiştirmiş olabilir.

const latestSelectedId = useLatest(selectedId);

async function save() {
  await api.saveDraft({ id: latestSelectedId.current, data: formData });
}

Bu yaklaşım “hangi render’ı yakaladım?” problemini ortadan kaldırır.


Ne zaman kullanmalı / ne zaman kaçınmalı?

Kullan:

  • addEventListener, setInterval, setTimeout, WebSocket callback’leri
  • Üçüncü parti kütüphane callback’lerine en güncel değerleri vermek

Kaçın:

  • Sırf dependency listesi kısa görünsün diye. Eğer effect gerçekten value değişince yeniden kurulmalıysa, ref ile bunu “masklemek” hataya yol açabilir.

Sonuç

useRef yalnızca DOM referansı değildir; doğru kullanıldığında React’te stabil callback + güncel veri ihtiyacını temizce çözer. “Latest value” deseni, özellikle event listener ve async akışlarda stale closure kaynaklı bug’ları ciddi şekilde azaltır.