İçeriğe geç
Sedat Demir
Geri dön

React'te useOptimistic Hook ile Anlık UI Güncellemeleri: Kullanıcı Deneyimini Devrimleştirin

React'te useOptimistic Hook ile Anlık UI Güncellemeleri: Kullanıcı Deneyimini Devrimleştirin

Günümüz web uygulamalarında kullanıcı deneyimi (UX) en kritik unsurlardan biridir. Kullanıcılar, hızlı, akıcı ve anında geri bildirim veren arayüzlere alışıktır. Bir butona tıkladığınızda veya bir form gönderdiğinizde, uygulamanın yanıt vermesi için beklemek kullanıcıyı hayal kırıklığına uğratabilir. İşte bu noktada "optimistic UI" (iyimser kullanıcı arayüzü) kavramı devreye girer.

Optimistic UI, bir işlemin başarılı olacağını varsayarak, işlemin tamamlanmasını beklemeden arayüzde hemen güncellemeler yapma tekniğidir. Bu, kullanıcıya anında geri bildirim sağlar ve uygulamanın daha hızlı ve duyarlı görünmesini sağlar. Ancak, optimistic UI'ı doğru bir şekilde uygulamak bazen karmaşık olabilir, özellikle de işlemin başarısız olma ihtimali varsa.

React 19 ile birlikte gelen useOptimistic hook'u, optimistic UI desenini uygulamayı inanılmaz derecede kolaylaştırır. Bu hook, bir işlemin sonucunu beklerken arayüzde geçici güncellemeler yapmanıza olanak tanır ve işlemin kendisi tamamlandığında bu geçici güncellemeleri doğru durumla değiştirir. Bu yazıda, useOptimistic hook'unu derinlemesine inceleyecek, nasıl çalıştığını anlayacak ve gerçek dünya senaryolarında nasıl kullanabileceğinizi göreceğiz.

Optimistic UI Nedir ve Neden Önemlidir?

Optimistic UI, bir işlemin (örneğin, bir veriyi kaydetme, bir öğeyi silme veya bir beğeni gönderme) sunucu tarafından onaylanmasını beklemeden, hemen arayüzde bir güncelleme yapılmasıdır. İşlem başarılı olursa, bu geçici güncelleme kalıcı hale gelir. İşlem başarısız olursa, arayüz önceki durumuna geri döner veya bir hata mesajı gösterilir.

Optimistic UI'ın Faydaları:

Ancak, optimistic UI'ın bir dezavantajı da vardır: işlemin başarısız olma olasılığı. Eğer işlem başarısız olursa ve kullanıcıya doğru geri bildirim verilmezse, bu durum kafa karışıklığına ve güven kaybına yol açabilir. İşte useOptimistic hook'u bu noktada devreye girerek bu zorluğu ortadan kaldırır.

useOptimistic Hook'u Nedir ve Nasıl Çalışır?

useOptimistic hook'u, React'in sunucu durumu yönetimi (Server State Management) konusundaki yaklaşımını basitleştirmek için tasarlanmış yeni bir hook'tur. Temel olarak, bir durum değişkenini yönetir ve bu durum değişkeninin bir "iyimser" (optimistic) değerini tutar. Bir işlem başlatıldığında, bu iyimser değer hemen güncellenir. İşlem tamamlandığında (başarılı veya başarısız), hook bu iyimser değeri gerçek sunucu durumuna göre günceller.

useOptimistic hook'u iki argüman alır:

  1. currentState: Mevcut gerçek (sunucu tarafından onaylanmış) durumunuz.
  2. optimisticUpdate: Bu, bir fonksiyon veya bir değer olabilir. Bir işlem başlatıldığında, optimisticUpdate fonksiyonu çağrılır ve yeni iyimser durumu döndürür.

Hook, currentState'in bir kopyasını döndürür. Bu döndürülen değer, ya mevcut gerçek durumunuzu ya da işlemin sonucunu beklerken güncellenmiş iyimser durumunuzu temsil eder.

Temel Kullanım Yapısı:

import { useOptimistic } from 'react';

function MyComponent({ initialItems }) {
  const [items, setItems] = useOptimistic(
    initialItems,
    (currentState, newItem) => {
      // newItem, eklemek istediğiniz yeni öğeyi temsil eder.
      // currentState, mevcut öğeler dizisidir.
      // Bu fonksiyon, yeni iyimser durumu döndürmelidir.
      return [...currentState, newItem];
    }
  );

  const addItem = async (itemData) => {
    // Sunucuya öğeyi ekleme işlemini başlatın.
    // Bu işlem asenkron olacaktır.

    // İşlem tamamlanmadan önce iyimser olarak öğeyi ekleyin.
    // useOptimistic hook'u bunu sizin için yönetecektir.
    setItems(itemData); // Bu satır, hook'un optimisticUpdate fonksiyonunu tetikler.

    // Sunucuya gerçek ekleme işlemini yapın.
    try {
      await fakeApiCallToAddItem(itemData);
      // İşlem başarılı olursa, hook otomatik olarak gerçek durumu güncelleyecektir.
    } catch (error) {
      // İşlem başarısız olursa, hook durumu geri alacaktır.
      console.error("Öğe eklenemedi:", error);
      // Hata durumunda kullanıcıya bilgi verebilirsiniz.
    }
  };

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
      <button onClick={() => addItem({ id: Date.now(), name: "Yeni Öğe" })}>
        Öğe Ekle
      </button>
    </div>
  );
}

// Sahte API çağrısı (gerçek bir API çağrısı ile değiştirilmelidir)
const fakeApiCallToAddItem = (item) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.2) { // %80 başarı oranı
        resolve();
      } else {
        reject(new Error("Sunucu hatası"));
      }
    }, 1000);
  });
};

Yukarıdaki örnekte:

useOptimistic Hook'u ile Yaygın Kullanım Senaryoları

useOptimistic hook'u, çeşitli kullanıcı etkileşimlerinde akıcı bir deneyim sağlamak için kullanılabilir. İşte bazı yaygın senaryolar:

1. Öğe Ekleme/Silme

Bir liste veya koleksiyona yeni bir öğe eklerken veya mevcut bir öğeyi silerken, kullanıcıya anında geri bildirim vermek deneyimi büyük ölçüde iyileştirir.

Örnek: Görev Listesi

import { useOptimistic, useState, useTransition } from 'react';

interface Task {
  id: number;
  text: string;
  done: boolean;
}

// Sahte API çağrıları
const fakeApi = {
  addTask: async (text: string): Promise<Task> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.1) { // %90 başarı
          resolve({ id: Date.now(), text, done: false });
        } else {
          reject(new Error('Görev eklenemedi'));
        }
      }, 500);
    });
  },
  toggleTask: async (id: number, done: boolean): Promise<void> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.1) { // %90 başarı
          resolve();
        } else {
          reject(new Error('Görev durumu güncellenemedi'));
        }
      }, 500);
    });
  },
  deleteTask: async (id: number): Promise<void> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.1) { // %90 başarı
          resolve();
        } else {
          reject(new Error('Görev silinemedi'));
        }
      }, 500);
    });
  },
};

function TodoList() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [isAdding, startTransition] = useTransition(); // UI'da geçişleri yönetmek için

  const [optimisticTasks, addOptimisticTask] = useOptimistic<Task[], Task>(
    tasks,
    (currentState, newTask) => [...currentState, newTask]
  );

  const handleAddTask = async (text: string) => {
    startTransition(async () => {
      const newTask = { id: Date.now(), text, done: false }; // Geçici ID
      addOptimisticTask(newTask); // İyimser olarak ekle

      try {
        const addedTask = await fakeApi.addTask(text);
        // Sunucu onayladıktan sonra gerçek görevle değiştir
        setTasks(prevTasks => prevTasks.map(t => t.id === newTask.id ? addedTask : t));
        // Not: Bu, sunucudan gelen gerçek ID ile geçici ID'yi eşleştirmek için bir stratejidir.
        // Daha karmaşık durumlarda, sunucudan dönen ID'yi yönetmek için daha gelişmiş bir state yönetimi gerekebilir.
      } catch (error) {
        console.error(error);
        // Hata durumunda iyimser eklemeyi geri al
        setTasks(prevTasks => prevTasks.filter(t => t.id !== newTask.id));
      }
    });
  };

  const handleToggleTask = async (id: number) => {
    startTransition(async () => {
      const taskToToggle = tasks.find(t => t.id === id);
      if (!taskToToggle) return;

      const updatedTask = { ...taskToToggle, done: !taskToToggle.done };

      // İyimser olarak durumu değiştir
      setTasks(prevTasks => prevTasks.map(t => t.id === id ? updatedTask : t));

      try {
        await fakeApi.toggleTask(id, updatedTask.done);
        // Sunucu onayladı, durum zaten güncellendi.
      } catch (error) {
        console.error(error);
        // Hata durumunda iyimser değişikliği geri al
        setTasks(prevTasks => prevTasks.map(t => t.id === id ? taskToToggle : t));
      }
    });
  };

  const handleDeleteTask = async (id: number) => {
    startTransition(async () => {
      const taskToDelete = tasks.find(t => t.id === id);
      if (!taskToDelete) return;

      // İyimser olarak sil
      setTasks(prevTasks => prevTasks.filter(t => t.id !== id));

      try {
        await fakeApi.deleteTask(id);
        // Sunucu onayladı, zaten silindi.
      } catch (error) {
        console.error(error);
        // Hata durumunda iyimser silmeyi geri al
        setTasks(prevTasks => [...prevTasks, taskToDelete]);
      }
    });
  };

  return (
    <div>
      <h1>Görev Listesi</h1>
      <input
        type="text"
        placeholder="Yeni görev ekle"
        onKeyDown={(e) => {
          if (e.key === 'Enter' && e.currentTarget.value.trim()) {
            handleAddTask(e.currentTarget.value.trim());
            e.currentTarget.value = '';
          }
        }}
      />
      {isAdding && <p>Yükleniyor...</p>}
      <ul>
        {optimisticTasks.map(task => (
          <li key={task.id} style={{ textDecoration: task.done ? 'line-through' : 'none' }}>
            <span onClick={() => handleToggleTask(task.id)} style={{ cursor: 'pointer' }}>
              {task.text}
            </span>
            <button onClick={() => handleDeleteTask(task.id)}>Sil</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Bu örnekte useOptimistic'i doğrudan kullanmak yerine, durumu doğrudan setTasks ile güncelleyip, useTransition ile UI'da geçişleri yönettik. useOptimistic hook'u aslında React'in gelecekteki sürümlerinde bu tür durum yönetimini daha da basitleştirecek bir mekanizma olarak düşünülmelidir. Mevcut durumda, useOptimistic'in temel amacı, sunucuya giden bir işlemin sonucunu beklerken arayüzdeki bir değeri yönetmektir. Yukarıdaki örnekte, setTasks ile yapılan doğrudan güncellemeler useOptimistic'in yaptığı işi simüle eder.

Not: React'in useOptimistic hook'u, React 19 ile birlikte daha belirgin ve kullanışlı hale gelecektir. Yukarıdaki örnek, useOptimistic'in temel mantığını ve optimistic UI prensibini göstermektedir. Gelecekteki React sürümlerinde, useOptimistic'in API'ı ve kullanım şekli biraz daha farklılık gösterebilir.

2. Yorumlara Beğeni Ekleme/Kaldırma

Bir gönderiye yorum yapıldığında veya bir yoruma beğeni eklendiğinde, beğeni sayısının anında artması kullanıcıyı memnun eder.

import { useOptimistic, useState, useTransition } from 'react';

interface Post {
  id: number;
  content: string;
  likes: number;
}

// Sahte API
const fakeApi = {
  likePost: async (postId: number): Promise<number> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.1) {
          resolve(1); // Başarılı olursa 1 beğeni eklenir
        } else {
          reject(new Error('Beğeni eklenemedi'));
        }
      }, 300);
    });
  },
  unlikePost: async (postId: number): Promise<number> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (Math.random() > 0.1) {
          resolve(-1); // Başarılı olursa 1 beğeni eksiltilir
        } else {
          reject(new Error('Beğeni kaldırılamadı'));
        }
      }, 300);
    });
  },
};

function PostWithLikes({ initialPost }: { initialPost: Post }) {
  const [post, setPost] = useState<Post>(initialPost);
  const [isLiking, startTransition] = useTransition();

  const [optimisticLikes, updateOptimisticLikes] = useOptimistic<number, number>(
    post.likes,
    (currentLikes, increment) => currentLikes + increment
  );

  const handleLikeToggle = async () => {
    startTransition(async () => {
      const previousLikes = post.likes;
      const increment = post.likes === initialPost.likes ? 1 : -1; // Basit bir varsayım, daha karmaşık mantık gerekebilir

      updateOptimisticLikes(increment); // İyimser olarak beğeni sayısını güncelle

      try {
        let result: number;
        if (increment === 1) {
          result = await fakeApi.likePost(post.id);
        } else {
          result = await fakeApi.unlikePost(post.id);
        }
        // Sunucu onayladı, iyimser güncelleme zaten yapıldı.
        // Gerçek beğeni sayısını sunucudan gelenle güncelleyebiliriz (opsiyonel).
        setPost(prev => ({ ...prev, likes: prev.likes + result }));
      } catch (error) {
        console

Share this post on:

Önceki Yazı
Fine-tuning vs RAG: Yapay Zeka Modellerini Özelleştirmenin İki Yolu
Sonraki Yazı
React 19 ile Yeni Bir Dönem: Server Actions ve Beklenen Yenilikler