---
title: "Supabase Realtime ile React'te Canlı Veri Takibi"
description: "Supabase Realtime ile React'te gerçek zamanlı veri nasıl takip edilir? postgres_changes, broadcast, channel yönetimi ve memory leak önleme rehberi."
date: 2026-04-13
category: react-supabase
tags: ["supabase", "realtime", "react", "websocket", "typescript", "dashboard"]
url: https://mikroerp.dev/blog/supabase-realtime-react-canli-veri/
---

## Gerçek Zamanlı Güncelleme Neden Önemli?

Dashboard'da satış verisi güncellendi — ama kullanıcı sayfayı yenilemeden görmüyor. Chat mesajı geldi — karşı taraf anlamıyor. Stok düştü — ilgili kişi saatler sonra fark ediyor.

Supabase Realtime bu sorunları **WebSocket** üzerinden çözer.

## Temel Kullanım: Tablo Dinleme

```tsx
import { useEffect, useState } from 'react';
import { supabase } from '../lib/supabase';

function LiveOrders() {
  const [orders, setOrders] = useState<Order[]>([]);

  useEffect(() => {
    // Mevcut siparişleri çek
    supabase.from('orders').select('*').order('created_at', { ascending: false })
      .then(({ data }) => setOrders(data || []));

    // Gerçek zamanlı dinlemeye başla
    const channel = supabase
      .channel('orders-changes')
      .on(
        'postgres_changes',
        { event: 'INSERT', schema: 'public', table: 'orders' },
        (payload) => {
          setOrders(prev => [payload.new as Order, ...prev]);
        }
      )
      .on(
        'postgres_changes',
        { event: 'UPDATE', schema: 'public', table: 'orders' },
        (payload) => {
          setOrders(prev => prev.map(o => 
            o.id === payload.new.id ? payload.new as Order : o
          ));
        }
      )
      .subscribe();

    // Cleanup — memory leak önleme
    return () => {
      supabase.removeChannel(channel);
    };
  }, []);

  return (
    <ul>
      {orders.map(order => (
        <li key={order.id}>{order.customer} — ₺{order.total}</li>
      ))}
    </ul>
  );
}
```

## Custom Hook: useRealtimeQuery

```tsx
import { useEffect, useState, useCallback } from 'react';
import { supabase } from '../lib/supabase';
import { RealtimePostgresChangesPayload } from '@supabase/supabase-js';

export function useRealtimeQuery<T extends { id: string }>(
  table: string,
  query?: string
) {
  const [data, setData] = useState<T[]>([]);
  const [loading, setLoading] = useState(true);

  // İlk veriyi çek
  const fetchData = useCallback(async () => {
    const { data: rows } = await supabase
      .from(table)
      .select(query || '*')
      .order('created_at', { ascending: false });
    setData((rows as T[]) || []);
    setLoading(false);
  }, [table, query]);

  useEffect(() => {
    fetchData();

    const channel = supabase
      .channel(`${table}-realtime`)
      .on(
        'postgres_changes',
        { event: '*', schema: 'public', table },
        (payload: RealtimePostgresChangesPayload<T>) => {
          if (payload.eventType === 'INSERT') {
            setData(prev => [payload.new as T, ...prev]);
          } else if (payload.eventType === 'UPDATE') {
            setData(prev => prev.map(item => 
              item.id === (payload.new as T).id ? payload.new as T : item
            ));
          } else if (payload.eventType === 'DELETE') {
            setData(prev => prev.filter(item => 
              item.id !== (payload.old as T).id
            ));
          }
        }
      )
      .subscribe();

    return () => { supabase.removeChannel(channel); };
  }, [table, fetchData]);

  return { data, loading, refetch: fetchData };
}

// Kullanım
function Dashboard() {
  const { data: orders, loading } = useRealtimeQuery<Order>('orders');
  if (loading) return <Skeleton />;
  return <OrderTable data={orders} />;
}
```

## RLS + Realtime Güvenliği

Realtime, RLS politikalarına **tabidir**. Kullanıcı sadece yetkili olduğu satırları alır:

```sql
-- Yalnızca kendi tenant'ının siparişlerini görsün
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY "tenant_orders" ON orders
  FOR SELECT
  USING (tenant_id = (auth.jwt() -> 'app_metadata' ->> 'tenant_id')::uuid);
```

> ⚠️ RLS olmadan tüm INSERT/UPDATE/DELETE eventleri tüm dinleyicilere gider!

## Broadcast: Ephemeral Mesajlaşma

Database'e yazılmayan, anlık mesajlar:

```tsx
// Cursor pozisyonu paylaşma (Google Docs gibi)
const channel = supabase.channel('room-1');

// Gönder
channel.send({
  type: 'broadcast',
  event: 'cursor',
  payload: { x: 100, y: 200, user: 'Ahmet' },
});

// Dinle
channel.on('broadcast', { event: 'cursor' }, ({ payload }) => {
  moveCursor(payload.x, payload.y, payload.user);
}).subscribe();
```

## Dikkat Edilecekler

1. **Cleanup zorunlu**: `useEffect` return'ünde `removeChannel` çağrın — yoksa memory leak.
2. **Duplicate subscription**: Component re-render'da yeni channel oluşmaz — dependency array doğru olmalı.
3. **Büyük tablolar**: Tüm değişiklikleri dinlemeyin, filtre kullanın.
4. **Offline durumu**: WebSocket koparsa Supabase otomatik reconnect yapar.

## İlgili Yazılar

- [PostgreSQL NOTIFY/LISTEN](/blog/postgresql-notify-listen-gercek-zamanli/) — Realtime'ın altyapısı
- [Supabase Auth](/blog/supabase-auth-react-kullanici-yonetimi/) — Session + Realtime
- [Supabase RLS](/blog/supabase-rls-cok-katmanli-yetkilendirme/) — Realtime güvenliği
- [React Query Cache](/blog/react-query-supabase-cache-stratejisi/) — Realtime + cache senkronizasyonu

---

*Bu rehber [Supabase Realtime Docs](https://supabase.com/docs/guides/realtime) dokümantasyonuna dayanarak hazırlanmıştır.*