ViraStack
  • Academy
  • About
  • Support
HomeSourcesContribute
ViraStack

A frontend ecosystem: free guides, project templates, AI rules, and form components.

Products

  • ViraStack Start
  • ViraStack AI
  • ViraStack Mask
  • ViraStack Password
  • ViraStack Guide

Explore

  • Academy
  • About
  • Brand
  • Support

© 2026 ViraStack.

Open source · MIT

Zustand

08 / 09

TanStack Query ile birlikte kullanım

Sunucu verisini TanStack Query'de, client state'i Zustand'da tutmak, store'daki filtreyle sorguyu sürmek ve state'i senkronlamak yerine türetmek.

Görev Paylaşımı

İki kütüphane farklı işler yapar ve birbirinin yerine geçmez:12

VeriÖrnekNerede
Sunucunun sahibi olduğu veriÜrünler, stok, siparişTanStack Query
Yalnızca tarayıcıda yaşayan veriSeçili filtreler, sepet çekmecesi, seçili satırZustand

Kural basit: sunucudan gelen veriyi store'a kopyalamayın. Kopya, sunucudaki veri değiştiğinde eskir; yükleme, hata ve yenileme durumlarını elle yazmak zorunda kalırsınız.2 Bu derste ikisini birleştirmenin iki kalıbı var.

Store Sorguyu Sürer

Filtreler client state'tir; ürün listesi server state. Filtreyi store'da tutup query key'e koyduğunuzda, filtre değiştiği anda sorgu kendiliğinden yeniden çalışır. TkDodo iki hook'u bir custom hook'ta birleştirir:3

// src/features/products/filter-store.ts
import { create } from "zustand";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { productQueries } from "./queries";

type FilterState = {
  category: string;
  search: string;
  actions: {
    setCategory: (category: string) => void;
    setSearch: (search: string) => void;
  };
};

const useFilterStore = create<FilterState>()((set) => ({
  category: "all",
  search: "",
  actions: {
    setCategory: (category) => set({ category }),
    setSearch: (search) => set({ search }),
  },
}));

export const useCategory = () => useFilterStore((state) => state.category);
export const useSearch = () => useFilterStore((state) => state.search);
export const useFilterActions = () => useFilterStore((state) => state.actions);

export function useFilteredProducts() {
  const category = useCategory();
  const search = useSearch();
  return useQuery({
    ...productQueries.list({ category, search }),
    placeholderData: keepPreviousData,
  });
}

Filtre paneli setCategory'yi çağırır; ürün listesi useFilteredProducts'ı kullanır. İkisi birbirini tanımaz. TkDodo bunu bildirimsel ve sihirsiz bulur: addFilter arayüzün herhangi bir yerinden çağrılabilir ve sorgu otomatik tetiklenir.3 keepPreviousData filtre değişirken eski listeyi ekranda tutar (bkz. Sayfalama ve sonsuz liste).

Tip

Filtrenin paylaşılabilir bir linkte ya da geri butonunda korunması gerekiyorsa store yerine URL'de tutun. Kalıp aynı kalır: URL'den okunan değer query key'e girer.4

Senkronlama Değil, Türetme

Sık görülen bir sorun: store'da seçili kullanıcının id'si duruyor, ama kullanıcı sunucudan silinmiş. İlk akla gelen, sorgu sonucu değişince store'u bir useEffect ile düzeltmektir:5

// ❌ Elle senkronlama
const { data: users } = useQuery(userQueries.list());
const selectedUserId = useSelectedUserId();
const { selectUser } = useUserActions();

useEffect(() => {
  if (!users?.some((u) => u.id === selectedUserId)) {
    selectUser(null);
  }
}, [users, selectedUserId, selectUser]);

TkDodo burada farklı bir soru sorar: "Kullanıcılar ve seçim şu; gerçek seçim hangisi?" Store'a dokunmadan, seçimi her render'da türetin:5

// ✅ Türetme
export function useSelectedUser() {
  const { data: users } = useQuery(userQueries.list());
  const selectedUserId = useSelectedUserId();

  return users?.find((u) => u.id === selectedUserId) ?? null;
}

Bunun ek faydaları var:5

  • Ekstra render ve useEffect yok.

  • Kullanıcı listeye geri eklenirse seçim kendiliğinden geri gelir; store'daki değer hiç silinmedi.

  • Arayüz kararı değişirse (seçimi silmek yerine "geçersiz" göstermek) yalnızca bu hook değişir.

Bedeli: store'daki selectedUserId'ye artık doğrudan güvenemezsiniz; her yerde custom hook'u kullanmalısınız. TkDodo bunu sorun saymaz: store, arayüzde ne seçildiğinin kaydıdır; doğrulanmış son değer değil.5

Aynı fikir sunucudan gelen varsayılan değerler için de geçerlidir. Sorgu gelince useEffect ile ilk kaydı seçmek yerine, kullanıcı henüz bir şey seçmediyse ilk kaydı gösterin:5

const selection = useSelectedProductId();
const { data: products } = useQuery(productQueries.list(filters));

const activeId = selection ?? products?.[0]?.id;

Bu yaklaşım Zustand'a özel değildir; useState ya da URL state'i için de aynı şekilde çalışır.5 Genel ilke React dokümanında da var: hesaplanabilen bir değer için Effect yazmayın.6

Sepet Örneği

Sepeti store'da saklamak mantıklıdır; kullanıcının seçimidir ve sunucuda henüz yoktur. Ama stok sunucunun verisidir. Sepetteki ürünün stoğu azaldıysa uyarıyı store'u güncelleyerek değil, iki kaynağı birleştirerek gösterin:

function CartLine({ id }: { id: number }) {
  const item = useCartItem(id); // Zustand: kullanıcının seçtiği adet
  const { data: product } = useQuery(productQueries.detail(id)); // TanStack Query: güncel stok

  if (!item) return null;
  const overStock = product !== undefined && item.quantity > product.stock;

  return (
    <li>
      {item.name} × {item.quantity}
      {overStock && <p role="alert">Stokta yalnızca {product.stock} adet var.</p>}
    </li>
  );
}

Kullanıcı adedi azaltınca ya da stok sunucuda artınca uyarı kendiliğinden kaybolur.

Kendin Dene

  • Ürün listesini önce kategori useState'te, sonra Zustand store'unda olacak şekilde yazın. Filtre paneli ile liste farklı component'lerdeyse hangisi daha az prop geçirir?

  • Seçili kullanıcıyı useEffect ile senkronlayın, sonra türeterek yazın. Kullanıcıyı sahte API'den silip geri ekleyin; iki versiyonda seçime ne oluyor?

  • Sepetteki bir ürünün stoğunu sahte API'de düşürün ve invalidateQueries çağırın. Uyarı görünüyor mu?

Detaylı Öğrenmek İçin

  • Yazı: TkDodo, "Deriving Client State from Server State"; senkronlama yerine türetme ve varsayılan değerler.5

  • Yazı: TkDodo, "Working with Zustand"; store'u useQuery ile custom hook'ta birleştirmek.3

  • Resmi doküman: TanStack Query, "Does TanStack Query replace Redux, MobX or other global state managers?".2

Kaynaklar

  • 1ArticleKent C. Dodds

    : — 2020. Server cache ile UI state'i ayırmak.

  • 2DocsTanStack Query: Does TanStack Query replace Redux, MobX or other global state managers?

    Server state taşındıktan sonra global store'da kalan client state.

  • 3ArticleTkDodo

    : — 20 Kasım 2022. Store'daki filtreyle sorguyu sürmek ve custom hook ile birleştirmek.

  • 4VideoTkDodo

    : — React Day Berlin, Aralık 2024. Filtreyi URL'de tutup anahtara bağlamak.

  • 5ArticleTkDodo

    : — 1 Eylül 2025. `useEffect` ile senkronlamak yerine türetmek, ödünleşimi ve varsayılan değer örneği.

  • 6DocsReact: You Might Not Need an Effect

    Hesaplanabilen değer için Effect yazmamak.

Edit this page

Last updated: October 21, 2026

Previous step07. Context ile storeNext step09. Ürün kataloğu ve sepetProject