React 19 useEffectEvent ve Activity: Yeni API'ler ve 2026 Mulakat Sorulari
React 19.2 useEffectEvent ve Activity API detayli incelemesi. Stale closure cozumleri, arka plan on-render, kod ornekleri ve 2026 mulakat sorulari.

React 19.2, uzun süredir deneysel aşamada olan iki önemli API'yi kararlı sürüme taşıdı: useEffectEvent ve Activity. Bu iki API, React uygulamalarında sıkça karşılaşılan iki temel soruna çözüm sunuyor. useEffectEvent, effect içindeki stale closure problemini ortadan kaldırırken, Activity bileşeni arka planda ön-render işlemleri gerçekleştirerek kullanıcı deneyimini iyileştiriyor. Bu makale, her iki API'nin çalışma prensiplerini, kullanım kurallarını, yaygın tuzaklarını ve teknik mülakatlarda karşılaşılabilecek soruları kapsamlı şekilde ele almaktadır.
useEffectEvent ve Activity API'leri yalnızca React 19.2 ve sonraki sürümlerde kararlı olarak kullanılabilir. Proje bağımlılıklarının en az react@19.2.0 sürümüne güncellendiğinden emin olunmalıdır.
Stale Closure Problemi ve useEffectEvent Çözümü
React geliştiricilerinin en sık karşılaştığı sorunlardan biri, useEffect içindeki callback fonksiyonlarının eski (stale) değerlere referans vermesidir. Bu durum, bir effect'in bağımlılık dizisinde yer almayan ancak callback içinde kullanılan değişkenlerin güncel halini okuyamamasından kaynaklanır. Geleneksel çözüm, useRef kullanarak değeri manuel olarak güncel tutmaktı; ancak bu yaklaşım hem okunabilirliği azaltır hem de linter uyarılarına neden olur.
Aşağıdaki örnekte, bir sohbet odası bağlantısı yönetilirken theme değerinin güncel kalması için useRef ile geçici bir çözüm uygulanmıştır:
// Before useEffectEvent: useRef workaround
import { useEffect, useRef } from 'react'
export function useChatRoom(roomId: string, theme: string) {
// Store theme in a ref to avoid stale closure
const themeRef = useRef(theme)
themeRef.current = theme
useEffect(() => {
const connection = createConnection(roomId)
connection.on('message', (msg) => {
// themeRef.current always has the latest value
logAnalytics('new_message', { roomId, theme: themeRef.current })
showNotification(msg)
})
connection.connect()
return () => connection.disconnect()
}, [roomId]) // theme intentionally excluded — but linter warns
}Bu kod çalışıyor olsa da, birkaç ciddi dezavantajı bulunmaktadır. İlk olarak, theme bağımlılık dizisinden kasıtlı olarak çıkarıldığı için ESLint exhaustive-deps kuralı uyarı verir. İkinci olarak, useRef ile yapılan bu geçici çözüm, kodun asıl amacını gizler ve bakım maliyetini artırır. Üçüncü olarak, ekip üyelerinin bu pattern'i anlaması ve doğru uygulaması gerekir, bu da hata riskini yükseltir.
useEffectEvent hook'u bu sorunu kökünden çözer. Bir Effect Event olarak tanımlanan fonksiyon, her zaman en güncel prop ve state değerlerini okur, ancak effect'in yeniden çalışmasını tetiklemez:
// After useEffectEvent: clean separation of concerns
import { useEffect, useEffectEvent } from 'react'
export function useChatRoom(roomId: string, theme: string) {
// Effect Event: always reads latest theme, never triggers reconnect
const onMessage = useEffectEvent((msg: string) => {
logAnalytics('new_message', { roomId, theme })
showNotification(msg)
})
useEffect(() => {
const connection = createConnection(roomId)
connection.on('message', onMessage)
connection.connect()
return () => connection.disconnect()
}, [roomId]) // No linter warning — onMessage is an Effect Event
}Bu yaklaşımda theme değişkeni doğrudan useEffectEvent callback'i içinde okunmaktadır. React, bu fonksiyonun bir Effect Event olduğunu bildiği için, theme değişimlerinde effect'i yeniden çalıştırmaz ancak callback çağrıldığında her zaman güncel theme değerini kullanır. Linter uyarısı da ortadan kalkar, çünkü onMessage bir Effect Event olarak tanınır ve bağımlılık dizisine eklenmesi gerekmez.
useEffectEvent Kuralları ve Kısıtlamaları
useEffectEvent son derece güçlü bir araç olsa da, belirli kurallar dahilinde kullanılmalıdır. Bu kurallar, API'nin doğru çalışmasını garanti altına alır ve olası hataların önüne geçer.
Kural 1: Yalnızca useEffect içinden çağrılmalıdır. Bir Effect Event, doğrudan event handler olarak veya render sırasında çağrılamaz. Yalnızca bir useEffect bloğunun içinden tetiklenmelidir.
Kural 2: Asla başka bileşenlere veya hook'lara aktarılmamalıdır. Effect Event fonksiyonları, tanımlandıkları bileşenin kapsamında kalmalıdır. Props olarak alt bileşenlere geçirilmesi veya başka hook'lara parametre olarak verilmesi desteklenmez.
Kural 3: Bağımlılık dizisine dahil edilmez. React, useEffectEvent ile oluşturulan fonksiyonları özel olarak işler ve bunların bağımlılık dizisine eklenmesini gerektirmez.
Aşağıdaki örnek, bu kuralların doğru uygulandığı bir arama izleme bileşenini göstermektedir:
// Correct: Effect Event used inside useEffect
import { useEffect, useEffectEvent, useState } from 'react'
interface SearchTrackerProps {
query: string
userId: string
}
export function SearchTracker({ query, userId }: SearchTrackerProps) {
const [results, setResults] = useState<string[]>([])
// Track searches with current user context
const onSearchComplete = useEffectEvent((resultCount: number) => {
analytics.track('search_complete', {
query,
userId, // Always latest userId
resultCount,
timestamp: Date.now(),
})
})
useEffect(() => {
const controller = new AbortController()
fetchSearchResults(query, controller.signal).then((data) => {
setResults(data)
onSearchComplete(data.length) // Called from inside useEffect
})
return () => controller.abort()
}, [query]) // userId excluded safely — lives in the Effect Event
return <ResultsList results={results} />
}Bu örnekte userId değişkeni bağımlılık dizisinden güvenle çıkarılmıştır. Kullanıcı kimliği değiştiğinde arama yeniden tetiklenmez, ancak analitik izleme her zaman güncel userId değerini kullanır. Bu, reaktif davranış (arama sorgusuna tepki verme) ile reaktif olmayan davranış (güncel bağlamı okuma) arasındaki ayrımı net bir şekilde ortaya koyar.
useEffectEvent ile oluşturulan fonksiyonların render sırasında veya JSX içinden doğrudan çağrılması çalışma zamanı hatasına neden olur. Bu fonksiyonlar yalnızca useEffect callback'i içinden çağrılmalıdır. onClick gibi DOM event handler'larında kullanılması gereken durumlarda, standart bir fonksiyon tanımı tercih edilmelidir.
Activity Bileşeni: Arka Planda Ön-Render
Activity, React 19.2 ile birlikte gelen ve bileşenlerin arka planda ön-render edilmesini sağlayan yeni bir yerleşik bileşendir. Daha önce <Offscreen> adıyla deneysel olarak geliştirilen bu API, artık kararlı sürümde Activity adıyla kullanılmaktadır. Temel amacı, kullanıcının henüz görmediği ancak kısa süre içinde görme ihtimali yüksek olan içerikleri önceden hazırlamaktır.
Activity bileşeni, mode prop'u aracılığıyla iki farklı modda çalışır: 'visible' ve 'hidden'. visible modunda bileşen normal şekilde render edilir ve kullanıcıya gösterilir. hidden modunda ise bileşen DOM'a düşük öncelikle render edilir, ancak kullanıcıya gösterilmez. Önemli olan nokta, hidden modundaki bileşenlerin state'inin korunmasıdır. Bu sayede bir bileşen hidden'dan visible'a geçtiğinde, tüm form verileri, scroll pozisyonu ve yerel state aynen kaldığı yerden devam eder.
Aşağıdaki örnek, sekme geçişlerinde form durumunun korunmasını sağlayan bir tab layout bileşenini göstermektedir:
// Activity preserves form state across tab switches
import { useState } from 'react'
import { Activity } from 'react'
interface Tab {
id: string
label: string
content: React.ReactNode
}
export function TabLayout({ tabs }: { tabs: Tab[] }) {
const [activeTab, setActiveTab] = useState(tabs[0].id)
return (
<div>
<nav className="flex gap-2 border-b border-border">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={activeTab === tab.id ? 'border-b-2 border-primary' : ''}
>
{tab.label}
</button>
))}
</nav>
{tabs.map((tab) => (
<Activity key={tab.id} mode={activeTab === tab.id ? 'visible' : 'hidden'}>
{tab.content}
</Activity>
))}
</div>
)
}Bu yaklaşımda her sekme her zaman render edilmektedir, ancak yalnızca aktif sekme kullanıcıya gösterilir. Geleneksel koşullu render yaklaşımında ({activeTab === tab.id && tab.content}) sekme değiştirildiğinde bileşen unmount olur ve tüm yerel state kaybolur. Activity ile bu sorun tamamen ortadan kalkar.
Ön-Render ile Rota Hazırlama
Activity bileşeninin en güçlü kullanım alanlarından biri, kullanıcının bir sonraki adımda ziyaret etme olasılığı yüksek olan rotaları arka planda hazırlamaktır. Bu teknik, algılanan yükleme süresini önemli ölçüde azaltır:
// Pre-render the next likely route in the background
import { Activity, Suspense, use } from 'react'
interface PrerenderedRouteProps {
isActive: boolean
dataPromise: Promise<DashboardData>
}
export function PrerenderedRoute({ isActive, dataPromise }: PrerenderedRouteProps) {
return (
<Activity mode={isActive ? 'visible' : 'hidden'}>
<Suspense fallback={<DashboardSkeleton />}>
<DashboardContent dataPromise={dataPromise} />
</Suspense>
</Activity>
)
}
function DashboardContent({ dataPromise }: { dataPromise: Promise<DashboardData> }) {
// use() reads the cached promise — works during hidden pre-render
const data = use(dataPromise)
return (
<div className="grid grid-cols-3 gap-4">
<MetricsCard data={data.metrics} />
<ChartPanel data={data.charts} />
<RecentActivity items={data.activity} />
</div>
)
}Bu örnekte use() hook'u, promise'i okuyarak veriyi çözer. hidden modunda bu işlem arka planda gerçekleşir ve kullanıcı ilgili rotaya geçtiğinde içerik anında görüntülenir. Suspense ile birlikte kullanıldığında, veri henüz hazır değilken iskelet (skeleton) bileşeni gösterilir ve veri geldiğinde otomatik olarak asıl içerik ile değiştirilir.
React / Next.js mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Activity ve TanStack Query Tuzağı
Activity bileşeni ile TanStack Query birlikte kullanıldığında dikkat edilmesi gereken kritik bir davranış bulunmaktadır. hidden modundaki bir Activity içindeki useQuery hook'ları veri çekme işlemini gerçekleştirmez. Bu, React'in hidden bileşenlerdeki effect'leri geciktirmesinden kaynaklanır ve beklenmedik boş state durumlarına yol açabilir.
// Problem: useQuery won't fetch when Activity is hidden
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Activity } from 'react'
function UserStats() {
// This useQuery will NOT run while hidden
const { data } = useQuery({
queryKey: ['user-stats'],
queryFn: fetchUserStats,
})
return <StatsDisplay data={data} />
}
// Solution: prefetch data outside the Activity boundary
function DashboardWithPrefetch({ showStats }: { showStats: boolean }) {
const queryClient = useQueryClient()
// Prefetch at the parent level — runs regardless of Activity mode
queryClient.ensureQueryData({
queryKey: ['user-stats'],
queryFn: fetchUserStats,
})
return (
<Activity mode={showStats ? 'visible' : 'hidden'}>
<UserStats />
</Activity>
)
}Çözüm, veri çekme işlemini Activity sınırının dışına taşımaktır. queryClient.ensureQueryData() yöntemi, üst bileşen seviyesinde çağrılarak Activity modundan bağımsız şekilde çalışır. Böylece bileşen visible moduna geçtiğinde, TanStack Query önbelleğinde zaten mevcut olan veriyi anında okur. Bu pattern, TanStack Query ile ilgili detaylı bilgiler için referans kaynağında incelenebilir.
Activity bileşeni hidden modundayken yalnızca render işlemi gerçekleşir; useEffect, useLayoutEffect ve bunlara bağlı abonelikler (TanStack Query dahil) çalışmaz. Bu davranış, arka plan render'ının yan etki üretmemesi ilkesine dayanır. Veri çekme işlemleri her zaman Activity sınırının dışında başlatılmalıdır.
useEffectEvent ve Activity Birlikte Kullanımı
Bu iki API'nin birlikte kullanıldığı senaryolar, özellikle gerçek zamanlı uygulamalarda güçlü bir mimari sunar. Aşağıdaki örnekte, çok kanallı bir canlı yayın paneli hem WebSocket bağlantı yönetimini hem de sekme geçişlerini sorunsuz şekilde gerçekleştirmektedir:
import { useEffect, useEffectEvent, useState } from 'react'
import { Activity } from 'react'
function LiveFeed({ channel, userId }: { channel: string; userId: string }) {
const [messages, setMessages] = useState<Message[]>([])
// Analytics tracking with latest userId — no effect re-sync
const onNewMessage = useEffectEvent((msg: Message) => {
analytics.track('live_message', { channel, userId })
setMessages((prev) => [...prev, msg])
})
useEffect(() => {
const ws = new WebSocket(`wss://api.example.com/${channel}`)
ws.onmessage = (event) => {
const msg = JSON.parse(event.data) as Message
onNewMessage(msg)
}
return () => ws.close()
}, [channel]) // Clean reconnect only when channel changes
return <MessageList messages={messages} />
}
export function LiveDashboard({ userId }: { userId: string }) {
const [activeChannel, setActiveChannel] = useState('general')
const channels = ['general', 'alerts', 'metrics']
return (
<div>
<nav className="flex gap-2">
{channels.map((ch) => (
<button key={ch} onClick={() => setActiveChannel(ch)}>
{ch}
</button>
))}
</nav>
{channels.map((ch) => (
<Activity key={ch} mode={activeChannel === ch ? 'visible' : 'hidden'}>
<LiveFeed channel={ch} userId={userId} />
</Activity>
))}
</div>
)
}Bu mimaride useEffectEvent, userId değiştiğinde WebSocket bağlantısının yeniden kurulmasını engellerken, Activity bileşeni kanal geçişlerinde mesaj geçmişinin kaybolmasını önler. Kullanıcı bir kanaldan diğerine geçip geri döndüğünde, tüm mesajlar yerinde kalır. hidden moddaki kanalların effect'leri çalışmadığından, gereksiz WebSocket bağlantıları da oluşmaz. Bu durum, performans ve kaynak yönetimi açısından önemli bir avantaj sağlar.
Teknik Mülakat Soruları
Bu bölümde, React Hooks mülakat soruları kapsamında useEffectEvent ve Activity API'leri ile ilgili sıkça sorulan soruları ve beklenen yanıtları ele alınmaktadır.
Soru 1: useEffectEvent, useCallback'ten ne açıdan farklıdır?
useCallback, memoize edilmiş bir fonksiyon döndürür ve bağımlılık dizisi değiştiğinde fonksiyonu yeniden oluşturur. Effect'in bağımlılık dizisine dahil edilmesi gerekir ve dahil edildiğinde effect'in yeniden çalışmasını tetikleyebilir. useEffectEvent ise her zaman güncel prop ve state değerlerini okuyan, ancak effect'in bağımlılık dizisine dahil edilmeyen ve effect'in yeniden çalışmasını asla tetiklemeyen bir fonksiyon döndürür.
Soru 2: Bir useEffectEvent fonksiyonu doğrudan bir onClick handler olarak kullanılabilir mi?
Hayır, kullanılamaz. useEffectEvent ile oluşturulan fonksiyonlar yalnızca useEffect içinden çağrılabilir. DOM event handler'larında doğrudan kullanım desteklenmez ve çalışma zamanı hatasına neden olur. Bu kuralın amacı, reaktif ve reaktif olmayan kod arasındaki sınırı netleştirmektir.
Soru 3: Activity bileşeni hidden modundayken useEffect çalışır mı?
Hayır, çalışmaz. hidden modunda React yalnızca bileşeni render eder, ancak effect'leri, layout effect'lerini ve bunlara bağlı abonelikleri çalıştırmaz. Effect'ler yalnızca bileşen visible moduna geçtiğinde tetiklenir. Bu davranış, arka plan render'ının yan etki üretmemesini garanti eder.
Soru 4: Activity ile koşullu render arasındaki temel fark nedir?
Koşullu render ({condition && <Component />}) bileşeni tamamen unmount eder ve yeniden mount ettiğinde tüm yerel state sıfırlanır. Activity ise bileşeni DOM'da tutarak state'ini korur. Form verileri, scroll pozisyonu ve bileşen içi değişkenler, mod geçişlerinde aynen kalır.
Soru 5: TanStack Query, hidden bir Activity içinde neden veri çekmez ve bu nasıl çözülür?
TanStack Query, useQuery hook'unun effect'lerine bağlıdır. hidden modunda effect'ler çalışmadığından, veri çekme işlemi başlatılmaz. Çözüm olarak queryClient.ensureQueryData() veya queryClient.prefetchQuery() yöntemleri Activity sınırının dışında çağrılmalıdır. Böylece veri, bileşenin mod durumundan bağımsız olarak hazır hale gelir.
Soru 6: useEffectEvent ve Activity birlikte kullanıldığında WebSocket bağlantı yönetimi nasıl etkilenir?
Activity bileşeni hidden modundayken effect'ler çalışmaz, dolayısıyla yeni WebSocket bağlantıları oluşturulmaz. Bileşen visible moduna geçtiğinde effect çalışır ve bağlantı kurulur. useEffectEvent sayesinde bağlantı callback'i her zaman güncel değerleri okur, ancak bağlantının kendisi yalnızca asıl bağımlılıklar (örneğin kanal adı) değiştiğinde yeniden kurulur. Bu kombinasyon, gereksiz bağlantı ve yeniden bağlantı maliyetini ortadan kaldırır.
Sonuc
useEffectEvent ve Activity, React 19.2 ile birlikte gelen ve farklı ancak birbirini tamamlayan sorunları çözen iki temel API'dir. useEffectEvent, stale closure sorununu temiz bir şekilde ortadan kaldırarak effect'lerdeki reaktif ve reaktif olmayan mantığın ayrılmasını sağlar. Activity ise bileşen state'ini koruyarak ve arka planda ön-render yaparak kullanıcı deneyimini önemli ölçüde iyileştirir. Her iki API'nin kurallarını ve kısıtlamalarını anlamak, hem üretim kodunda hatasız kullanım hem de teknik mülakatlarda başarılı performans için kritik öneme sahiptir. Detaylı bilgi için React 19.2 resmi duyurusu ve useEffectEvent referans dokümantasyonu incelenebilir.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

Next.js 16'da Cache Components: use cache, PPR ve 2026 Mülakat Soruları
Next.js 16 Cache Components rehberi: use cache yönergesi, Partial Pre-Rendering, cacheLife, cacheTag, use cache private ile güvenlik ve kıdemli geliştirici mülakat soruları.

2026'da React Compiler: Otomatik Memoizasyon ve Teknik Mülakat Soruları
React Compiler hakkında kapsamlı rehber — otomatik memoizasyon, derleme hattı, React kuralları, ESLint entegrasyonu ve 2026 yılı React teknik mülakat soruları.

Üretimde React Server Components: desenler ve tuzaklar
Üretimde React Server Components: savaşta sınanmış desenler, sık karşılaşılan anti-desenler ve sağlam Next.js 15 uygulamaları için hata ayıklama stratejileri.