Skip to content

📌 Ne Zaman Kullanılır?

  • ✅ SPA, dashboard, büyük ekip, karmaşık UI, mobil (React Native)
  • ⚠️ SEO gerekiyorsa Next.js kullan
  • ❌ Basit landing page (vanilla JS/HTML yeterli)

Önerilen Kullanım: React + TypeScript + Vite (SPA) veya Next.js (SSR/SSG) Alternatifler: Vue.js, Angular, Svelte, Solid

React Rehberi — Beginner to Advanced (Vite + JSX) (EN + TR)

Complete English + Turkish learning guide for pure React (Vite + JSX) — from fundamentals to modern ecosystem tools.
(Saf React (Vite + JSX) için Ingilizce + Turkce tam öğrenme rehberi — temellerden modern ekosisteme.)

1) Fundamentals (Temeller)

What is React? A library to build UI using components and state.
(React nedir? Bilesenler ve durum (state) ile arayuz insa eden bir kütüphane.)

Core ideas: components, props, state, unidirectional data flow.
(Temel fikirler: bilesenler, props, state, tek yonlu veri akisi.)

Create a project (Vite) — Proje oluşturma

bash
# Node 18+ onerilir
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

Visit: http://localhost:5173 (Vite default).

File anatomy (Dosya yapisi):

src/
 ├─ assets/
 ├─ App.jsx
 ├─ main.jsx   # React root
 └─ index.css

Hello Component (Merhaba Bileseni):

jsx
function Hello({ name = "World" }) {
  return <h1>Hello, {name}!</h1>;
}
export default Hello;

Summary: You can create a Vite app and render your first component.
(Artik bir Vite projesi olusturup ilk bilesenini render edebilirsin.)


2) Components, Props & State

Components: Reusable UI blocks. (Tekrar kullanılabilir UI bloklari)
Props: Read-only inputs to components. (Salt-okunur girdiler)
State: Internal, mutable data. (Bilesene ozel degisebilir veri)

jsx
function Counter({ step = 1 }) {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + step)}>+{step}</button>
    </div>
  );
}

Rules (Kurallar):

  • Props are immutable. (Props degismez.)
  • State updates are async & batched. (State guncellemeleri toplu ve asenkron olabilir.)

Summary: Distinguish props vs state; use state for interactive UI.
(Props ve state farkini anladin; etkilesim için state kullan.)


3) Hooks Derinlemesine

React Hooks, fonksiyon bilesenlerinde state ve yan etkileri yonetmenin modern yoludur. Asagida her bir hook detayli olarak aciklanmistir.

3.1) useState

Bileşen icinde yerel state tutmak için kullanilir.

Temel kullanım:

jsx
const [count, setCount] = React.useState(0);

Updater function (Guncelleyici fonksiyon):

State guncellemesi mevcut degere bagli oldugunda updater function kullanmak guvenlidir. React, state guncellemelerini toplu (batch) yapar ve eski degeri referans alarak doğru sonuc verir.

jsx
function Counter() {
  const [count, setCount] = React.useState(0);

  function handleTripleClick() {
    // yanlis: count hep ayni deger olur
    // setCount(count + 1);
    // setCount(count + 1);
    // setCount(count + 1);

    // dogru: updater fn ile sirayla artar
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  }

  return <button onClick={handleTripleClick}>Count: {count}</button>;
}

Lazy initialization (Tembel başlatma):

Baslangic degeri hesaplanmasi pahaliysa, fonksiyon olarak verin. Bu fonksiyon yalnizca ilk renderda çalışır.

jsx
const [data, setData] = React.useState(() => {
  // pahalı hesaplama — sadece ilk renderda calisir
  return JSON.parse(localStorage.getItem("myData")) || [];
});

Object state (Nesne state):

State bir nesne oldugunda, spread operatoru ile immutable güncelleme yapilmalidir.

jsx
const [form, setForm] = React.useState({ name: "", email: "", age: 0 });

function updateField(field, value) {
  setForm(prev => ({ ...prev, [field]: value }));
}

// kullanim
<input
  value={form.name}
  onChange={e => updateField("name", e.target.value)}
/>

Dikkat edilecekler:

  • State guncellemesi asenkrondur; setCount sonrasi count hemen degismez.
  • Nesneleri mutate etmeyin, her zaman yeni referans olusturun.
  • Bilesenin gereksiz yere render olmamasini istiyorsaniz state'i mumkun oldugunca alt bilesende tutun (colocate).

3.2) useEffect

Yan etkiler (side effects) için kullanilir: API cagrilari, event listener ekleme, timer başlatma, DOM manipulasyonu vb.

Dependency array (Bagimlilik dizisi):

jsx
// Her renderda calisir (dependency yok)
React.useEffect(() => {
  console.log("her renderda");
});

// Sadece mount'ta calisir (bos dizi)
React.useEffect(() => {
  console.log("sadece mount");
}, []);

// userId degistiginde calisir
React.useEffect(() => {
  fetchUser(userId);
}, [userId]);

Cleanup (Temizlik):

Effect'ten dondurdugunuz fonksiyon, bileşen unmount oldugunda veya effect yeniden calistirilmadan once cagirilir. Memory leak ve stale state onlemek için kritik oneme sahiptir.

jsx
React.useEffect(() => {
  const controller = new AbortController();

  fetch(`/api/users/${id}`, { signal: controller.signal })
    .then(r => r.json())
    .then(setUser)
    .catch(err => {
      if (err.name !== "AbortError") setError(err);
    });

  return () => controller.abort(); // cleanup
}, [id]);

Timer ornegi:

jsx
React.useEffect(() => {
  const id = setInterval(() => {
    setSeconds(s => s + 1);
  }, 1000);

  return () => clearInterval(id);
}, []);

Event listener ornegi:

jsx
React.useEffect(() => {
  function handleResize() {
    setWidth(window.innerWidth);
  }
  window.addEventListener("resize", handleResize);
  return () => window.removeEventListener("resize", handleResize);
}, []);

Strict Mode davranisi:

React 18 Strict Mode'da development ortaminda effect'ler iki kez çalışır (mount -> unmount -> mount). Bu, cleanup fonksiyonunuzun doğru calisip calismadigini test eder. Production'da bu olmaz.

jsx
// StrictMode uyumlu effect
React.useEffect(() => {
  let active = true;

  fetchData().then(data => {
    if (active) setData(data);
  });

  return () => { active = false; };
}, []);

3.3) useContext

Prop drilling olmadan bilesenlere veri aktarmak için kullanilir. createContext ile context olusturulur, Provider ile deger saglanir, useContext ile tuketilir.

jsx
import { createContext, useContext, useState } from "react";

// 1. Context olustur
const AuthContext = createContext(null);

// 2. Provider bileseni
function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  function login(userData) {
    setUser(userData);
  }

  function logout() {
    setUser(null);
  }

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// 3. Custom hook ile tuketim
function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
}

// 4. Kullanim
function Navbar() {
  const { user, logout } = useAuth();
  return (
    <nav>
      {user ? (
        <>
          <span>{user.name}</span>
          <button onClick={logout}>Cikis</button>
        </>
      ) : (
        <a href="/login">Giris</a>
      )}
    </nav>
  );
}

// 5. App'te sar
function App() {
  return (
    <AuthProvider>
      <Navbar />
      <main>{/* ... */}</main>
    </AuthProvider>
  );
}

Dikkat: Context degeri degistiginde, o context'i tuketen tüm bilesenler yeniden render olur. Performans için context'leri amacina gore ayirin (AuthContext, ThemeContext vb.).


3.4) useReducer

Karmasik state mantigi için useState yerine kullanilir. Redux benzeri reducer pattern'i bileşen icinde uygular.

jsx
import { useReducer } from "react";

const initialState = {
  items: [],
  loading: false,
  error: null,
};

function reducer(state, action) {
  switch (action.type) {
    case "FETCH_START":
      return { ...state, loading: true, error: null };
    case "FETCH_SUCCESS":
      return { ...state, loading: false, items: action.payload };
    case "FETCH_ERROR":
      return { ...state, loading: false, error: action.payload };
    case "ADD_ITEM":
      return { ...state, items: [...state.items, action.payload] };
    case "REMOVE_ITEM":
      return {
        ...state,
        items: state.items.filter(item => item.id !== action.payload),
      };
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(reducer, initialState);

  async function loadItems() {
    dispatch({ type: "FETCH_START" });
    try {
      const res = await fetch("/api/todos");
      const data = await res.json();
      dispatch({ type: "FETCH_SUCCESS", payload: data });
    } catch (err) {
      dispatch({ type: "FETCH_ERROR", payload: err.message });
    }
  }

  return (
    <div>
      <button onClick={loadItems}>Yukle</button>
      {state.loading && <p>Yukleniyor...</p>}
      {state.error && <p>Hata: {state.error}</p>}
      <ul>
        {state.items.map(item => (
          <li key={item.id}>
            {item.title}
            <button
              onClick={() =>
                dispatch({ type: "REMOVE_ITEM", payload: item.id })
              }
            >
              Sil
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Ne zaman useReducer?

  • State gecisleri birbirine bagliysa (loading -> success -> error)
  • Birden fazla alt deger birbiriyle iliskiliyse
  • Action'lari loglama/debug ihtiyaci varsa

3.5) useCallback & useMemo

useMemo: Pahali hesaplamalarin sonucunu cache'ler. Bagimliliklar degismediginde yeniden hesaplamaz.

jsx
import { useMemo, useState } from "react";

function ProductList({ products, query }) {
  const filtered = useMemo(() => {
    console.log("filtreleme calisti");
    return products.filter(p =>
      p.name.toLowerCase().includes(query.toLowerCase())
    );
  }, [products, query]);

  return (
    <ul>
      {filtered.map(p => (
        <li key={p.id}>{p.name} - {p.price} TL</li>
      ))}
    </ul>
  );
}

useCallback: Fonksiyon referansini stabil tutar. Memoize edilmis alt bilesenlere fonksiyon gecerken kullanislidir.

jsx
import { useCallback, useState, memo } from "react";

const ExpensiveChild = memo(function ExpensiveChild({ onClick, label }) {
  console.log("ExpensiveChild renderdi:", label);
  return <button onClick={onClick}>{label}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  // useCallback olmadan: her renderda yeni fonksiyon -> child gereksiz render olur
  // useCallback ile: count degismedikce ayni referans kalir
  const increment = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <p>Count: {count}</p>
      <ExpensiveChild onClick={increment} label="Artir" />
    </div>
  );
}

Ne zaman kullanmali?

  • useMemo: Hesaplama gercekten pahaliysa (büyük liste filtreleme, sorting, agir donusumler)
  • useCallback: React.memo ile sarili child'a fonksiyon gecerken
  • Erken optimizasyon yapmayin; once profiler ile olcun, sonra optimize et

Ne zaman KULLANMA:

  • Basit hesaplamalar için (a + b gibi)
  • Fonksiyon referansinin degismesi sorun olustumuyorsa
  • Bagimlilik dizisi surekli degisiyorsa (cache'in faydasi kalmaz)

3.6) useRef

DOM referansi:

jsx
import { useRef, useEffect } from "react";

function AutoFocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="Otomatik focuslanir" />;
}

Mutable value (Renderlar arasi deger saklama):

useRef degeri degistiginde render tetiklemez. Bu özellik, render disinda bir deger saklamak için idealdir.

jsx
function StopWatch() {
  const [seconds, setSeconds] = useState(0);
  const [running, setRunning] = useState(false);
  const intervalRef = useRef(null);

  function start() {
    if (running) return;
    setRunning(true);
    intervalRef.current = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);
  }

  function stop() {
    clearInterval(intervalRef.current);
    setRunning(false);
  }

  function reset() {
    stop();
    setSeconds(0);
  }

  return (
    <div>
      <p>{seconds} saniye</p>
      <button onClick={start}>Baslat</button>
      <button onClick={stop}>Durdur</button>
      <button onClick={reset}>Sifirla</button>
    </div>
  );
}

forwardRef (Ref'i alt bilesene iletme):

jsx
import { forwardRef, useRef } from "react";

const FancyInput = forwardRef(function FancyInput(props, ref) {
  return <input ref={ref} className="fancy" {...props} />;
});

function Parent() {
  const inputRef = useRef(null);

  return (
    <div>
      <FancyInput ref={inputRef} placeholder="Fancy input" />
      <button onClick={() => inputRef.current.focus()}>Focusla</button>
    </div>
  );
}

3.7) React 18+ Hooks: useId, useDeferredValue, useTransition

useId: Sunucu ve istemci tarafinda tutarli benzersiz ID uretir. Erisilebilirlik (a11y) için form etiketlerinde kullanislidir.

jsx
import { useId } from "react";

function FormField({ label }) {
  const id = useId();
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </div>
  );
}

useDeferredValue: Pahali render islemlerini erteleyerek UI'in duyarli kalmasini sağlar.

jsx
import { useState, useDeferredValue, useMemo } from "react";

function Search({ items }) {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query);

  const filtered = useMemo(
    () => items.filter(i => i.name.includes(deferredQuery)),
    [items, deferredQuery]
  );

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Ara..."
      />
      {query !== deferredQuery && <p>Arama yapiliyor...</p>}
      <ul>
        {filtered.map(i => (
          <li key={i.id}>{i.name}</li>
        ))}
      </ul>
    </div>
  );
}

useTransition: Düşük oncelikli state guncellemelerini isaretler. UI'in yanit vermeye devam etmesini sağlar.

jsx
import { useState, useTransition } from "react";

function TabContainer() {
  const [tab, setTab] = useState("home");
  const [isPending, startTransition] = useTransition();

  function selectTab(nextTab) {
    startTransition(() => {
      setTab(nextTab); // dusuk oncelikli guncelleme
    });
  }

  return (
    <div>
      <nav>
        <button onClick={() => selectTab("home")}>Ana Sayfa</button>
        <button onClick={() => selectTab("posts")}>Yazilar</button>
        <button onClick={() => selectTab("settings")}>Ayarlar</button>
      </nav>
      {isPending && <p>Yukleniyor...</p>}
      <TabContent tab={tab} />
    </div>
  );
}

3.8) Custom Hooks

Tekrar eden mantigi bilesenden cikarip yeniden kullanılabilir hale getirmek için custom hook yazilir. Isimleri use ile baslamalidir.

useLocalStorage:

jsx
import { useState, useEffect } from "react";

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// kullanim
function Settings() {
  const [theme, setTheme] = useLocalStorage("theme", "light");
  return (
    <button onClick={() => setTheme(t => (t === "light" ? "dark" : "light"))}>
      Tema: {theme}
    </button>
  );
}

useFetch:

jsx
import { useState, useEffect } from "react";

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function fetchData() {
      setLoading(true);
      setError(null);
      try {
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const json = await res.json();
        setData(json);
      } catch (err) {
        if (err.name !== "AbortError") {
          setError(err);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchData();
    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

// kullanim
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <p>Yukleniyor...</p>;
  if (error) return <p>Hata: {error.message}</p>;
  return <h2>{user.name}</h2>;
}

useDebounce:

jsx
import { useState, useEffect } from "react";

function useDebounce(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// kullanim
function SearchBox() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    if (debouncedQuery) {
      // API cagrisini burada yap
      console.log("Arama:", debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={e => setQuery(e.target.value)}
      placeholder="Ara..."
    />
  );
}

Summary: Custom hook'lar bilesenlerdeki tekrar eden mantigi ayirir ve test edilebilir hale getirir.


4) Component Patterns

4.1) Composition (Bileşen Birlestirme)

React'te inheritance yerine composition tercih edilir. children prop'u ile bilesenler birlestirilebilir.

jsx
function Card({ children, title }) {
  return (
    <div className="card">
      {title && <h3 className="card-title">{title}</h3>}
      <div className="card-body">{children}</div>
    </div>
  );
}

function InfoCard() {
  return (
    <Card title="Bilgi">
      <p>Bu bir bilgi kartidir.</p>
      <button>Detay</button>
    </Card>
  );
}

Slot pattern (Named children):

jsx
function Layout({ header, sidebar, children }) {
  return (
    <div className="layout">
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

function App() {
  return (
    <Layout
      header={<Navbar />}
      sidebar={<SideMenu />}
    >
      <Dashboard />
    </Layout>
  );
}

4.2) Render Props

Bileşen mantigi paylasimi için fonksiyon-prop kullanilir. Hook'lardan once yaygin olan bu pattern, bazi durumlarda hala kullanislidir.

jsx
function MouseTracker({ render }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });

  function handleMove(e) {
    setPos({ x: e.clientX, y: e.clientY });
  }

  return (
    <div onMouseMove={handleMove} style={{ height: "100vh" }}>
      {render(pos)}
    </div>
  );
}

// kullanim
<MouseTracker
  render={({ x, y }) => (
    <p>
      Fare pozisyonu: {x}, {y}
    </p>
  )}
/>

4.3) Compound Components

Birbiriyle iliskili bilesenlerin iç state'i paylasarak birlikte calismasini sağlar. Esnek API tasarimi için idealdir.

jsx
import { createContext, useContext, useState } from "react";

const TabsContext = createContext();

function Tabs({ children, defaultTab }) {
  const [activeTab, setActiveTab] = useState(defaultTab);

  return (
    <TabsContext.Provider value={{ activeTab, setActiveTab }}>
      <div className="tabs">{children}</div>
    </TabsContext.Provider>
  );
}

function TabList({ children }) {
  return <div className="tab-list">{children}</div>;
}

function Tab({ value, children }) {
  const { activeTab, setActiveTab } = useContext(TabsContext);
  return (
    <button
      className={activeTab === value ? "tab active" : "tab"}
      onClick={() => setActiveTab(value)}
    >
      {children}
    </button>
  );
}

function TabPanel({ value, children }) {
  const { activeTab } = useContext(TabsContext);
  if (activeTab !== value) return null;
  return <div className="tab-panel">{children}</div>;
}

// kullanim
function App() {
  return (
    <Tabs defaultTab="profile">
      <TabList>
        <Tab value="profile">Profil</Tab>
        <Tab value="settings">Ayarlar</Tab>
        <Tab value="notifications">Bildirimler</Tab>
      </TabList>
      <TabPanel value="profile">Profil icerigi</TabPanel>
      <TabPanel value="settings">Ayarlar icerigi</TabPanel>
      <TabPanel value="notifications">Bildirimler icerigi</TabPanel>
    </Tabs>
  );
}

4.4) Higher-Order Components (HOC)

Bir bileseni sarar ve ek ozellikler ekler. Hook'lar tercih edilmekle birlikte, cross-cutting concern'ler için hala kullanilir.

jsx
function withAuth(WrappedComponent) {
  return function AuthenticatedComponent(props) {
    const { user } = useAuth();

    if (!user) {
      return <Navigate to="/login" />;
    }

    return <WrappedComponent {...props} user={user} />;
  };
}

// kullanim
const ProtectedDashboard = withAuth(Dashboard);

4.5) Controlled vs Uncontrolled Components

Controlled: React state ile yonetilir. Deger ve degisiklik handler'i React'te.

jsx
function ControlledInput() {
  const [value, setValue] = useState("");

  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  );
}

Uncontrolled: DOM kendi state'ini yonetir. ref ile degere erisilir.

jsx
function UncontrolledInput() {
  const inputRef = useRef(null);

  function handleSubmit(e) {
    e.preventDefault();
    console.log(inputRef.current.value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="" />
      <button>Gonder</button>
    </form>
  );
}

Hangisini secmeli?

  • Controlled: Anlik validasyon, koşullu disabling, format zorlama gerektiginde
  • Uncontrolled: Basit formlar, dosya inputlari, ucuncu parti kutuphanelerle entegrasyon

5) Lists, Keys & Conditional Rendering

jsx
{todos.map(todo => (
  <TodoItem key={todo.id} todo={todo} />
))}

Keys: Unique & stable (id tercih edilir).
Conditional: &&, ternary, early returns.

jsx
{isLoading ? <Spinner /> : <List />}
{error && <p role="alert">{error.message}</p>}

Summary: Use stable keys; keep conditions simple.
(Stabil key kullan; kosullari basit tut.)


6) Context & Custom Hooks

Context: Pass data deep without prop drilling.

jsx
const ThemeContext = React.createContext("light");
function App(){ return <ThemeContext.Provider value="dark"><Header/></ThemeContext.Provider> }
function Header(){ const theme = React.useContext(ThemeContext); return <div data-theme={theme}/>; }

Custom hooks: Encapsulate reusable logic.

jsx
function useLocalStorage(key, initial) {
  const [value, setValue] = React.useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initial;
  });
  React.useEffect(() => localStorage.setItem(key, JSON.stringify(value)), [key, value]);
  return [value, setValue];
}

Summary: Share logic via custom hooks; share data via context.
(Mantigi custom hook ile, veriyi context ile payla.)


7) State Management (Durum Yönetimi)

7.1) Genel Bakış

  • UI State (local): useState/useReducer, component-level.
    (Bilesene ozel durumlar -- modal acik/kapali, input degerleri.)
  • Global Client State: Redux Toolkit / Zustand.
    (Uygulama genelinde kullanici, tema gibi veriler.)
  • Server State (Remote Data): RTK Query veya TanStack Query.
    (API'den gelen, cache'lenmesi/yenilenmesi gereken veriler.)

Rule of thumb: Server state is not client state. Use a server-state library for fetching/cache/invalidations.
(Altin kural: Sunucu verisi, istemci verisi degildir. API verileri için cache/yenileme yönetimi olan kütüphane kullan.)

7.2) Karar Agaci: Hangi State Yonetimini Kullanmaliyim?

Veri nereden geliyor?
  |
  +-- API/Sunucu --> TanStack Query veya RTK Query
  |
  +-- Istemci (Client)
       |
       +-- Sadece bir bilesen mi kullaniyor?
       |     |
       |     +-- Evet --> useState veya useReducer
       |     +-- Hayir --> Kac bilesen paylasacak?
       |           |
       |           +-- 2-3 yakin bilesen --> State'i ust bilesene tasima (lifting state)
       |           +-- Cok sayida bilesen
       |                 |
       |                 +-- Basit deger (tema, dil) --> Context API
       |                 +-- Karmasik/sik guncellenen --> Zustand veya Redux Toolkit

7.3) Context API Detayli Örnek

jsx
import { createContext, useContext, useReducer } from "react";

const CartContext = createContext();

function cartReducer(state, action) {
  switch (action.type) {
    case "ADD":
      return [...state, action.payload];
    case "REMOVE":
      return state.filter(item => item.id !== action.payload);
    case "CLEAR":
      return [];
    default:
      return state;
  }
}

function CartProvider({ children }) {
  const [cart, dispatch] = useReducer(cartReducer, []);

  const addItem = (item) => dispatch({ type: "ADD", payload: item });
  const removeItem = (id) => dispatch({ type: "REMOVE", payload: id });
  const clearCart = () => dispatch({ type: "CLEAR" });
  const totalPrice = cart.reduce((sum, item) => sum + item.price, 0);

  return (
    <CartContext.Provider
      value={{ cart, addItem, removeItem, clearCart, totalPrice }}
    >
      {children}
    </CartContext.Provider>
  );
}

function useCart() {
  const context = useContext(CartContext);
  if (!context) throw new Error("useCart must be used within CartProvider");
  return context;
}

7.4) Zustand Store Ornegi

bash
npm i zustand
jsx
import { create } from "zustand";
import { persist, devtools } from "zustand/middleware";

const useCartStore = create(
  devtools(
    persist(
      (set, get) => ({
        items: [],
        addItem: (item) =>
          set(
            (state) => ({ items: [...state.items, item] }),
            false,
            "cart/addItem"
          ),
        removeItem: (id) =>
          set(
            (state) => ({
              items: state.items.filter((i) => i.id !== id),
            }),
            false,
            "cart/removeItem"
          ),
        clearCart: () => set({ items: [] }, false, "cart/clear"),
        get totalPrice() {
          return get().items.reduce((sum, i) => sum + i.price, 0);
        },
      }),
      { name: "cart-storage" } // localStorage key
    )
  )
);

// kullanim — sadece ihtiyac duyulan state'i sec (selector)
function CartIcon() {
  const itemCount = useCartStore((s) => s.items.length);
  return <span>Sepet ({itemCount})</span>;
}

function CartPage() {
  const { items, removeItem, clearCart } = useCartStore();
  return (
    <div>
      {items.map((item) => (
        <div key={item.id}>
          {item.name} - {item.price} TL
          <button onClick={() => removeItem(item.id)}>Sil</button>
        </div>
      ))}
      <button onClick={clearCart}>Sepeti Bosalt</button>
    </div>
  );
}

Zustand avantajlari:

  • Minimal API, boilerplate yok
  • Provider gerektirmez
  • Selector ile gereksiz render onlenir
  • Middleware destegi (persist, devtools, immer)

7.5) Redux Toolkit Slice Ornegi

bash
npm i @reduxjs/toolkit react-redux

Store setup:

jsx
// src/store/index.js
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./slices/cartSlice";
import authReducer from "./slices/authSlice";

export const store = configureStore({
  reducer: {
    cart: cartReducer,
    auth: authReducer,
  },
});

Slice:

jsx
// src/store/slices/cartSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

export const fetchCart = createAsyncThunk("cart/fetch", async (userId) => {
  const res = await fetch(`/api/cart/${userId}`);
  return res.json();
});

const cartSlice = createSlice({
  name: "cart",
  initialState: {
    items: [],
    status: "idle", // idle | loading | succeeded | failed
    error: null,
  },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload); // immer sayesinde mutate edebilirsin
    },
    removeItem: (state, action) => {
      state.items = state.items.filter((i) => i.id !== action.payload);
    },
    clearCart: (state) => {
      state.items = [];
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchCart.pending, (state) => {
        state.status = "loading";
      })
      .addCase(fetchCart.fulfilled, (state, action) => {
        state.status = "succeeded";
        state.items = action.payload;
      })
      .addCase(fetchCart.rejected, (state, action) => {
        state.status = "failed";
        state.error = action.error.message;
      });
  },
});

export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;

Provider ve kullanım:

jsx
// main.jsx
import { Provider } from "react-redux";
import { store } from "./store";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);

// Bilesen icinde
import { useSelector, useDispatch } from "react-redux";
import { addItem, removeItem } from "./store/slices/cartSlice";

function CartPage() {
  const { items, status } = useSelector((state) => state.cart);
  const dispatch = useDispatch();

  if (status === "loading") return <p>Yukleniyor...</p>;

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name}
          <button onClick={() => dispatch(removeItem(item.id))}>Sil</button>
        </li>
      ))}
    </ul>
  );
}

8) Routing with React Router (v6+)

8.1) Temel Kurulum: createBrowserRouter

bash
npm i react-router-dom
jsx
import {
  createBrowserRouter,
  RouterProvider,
  Outlet,
  Link,
  NavLink,
} from "react-router-dom";

// Layout bileseni
function RootLayout() {
  return (
    <div>
      <nav>
        <NavLink to="/" end>Ana Sayfa</NavLink>
        <NavLink to="/about">Hakkinda</NavLink>
        <NavLink to="/posts">Yazilar</NavLink>
      </nav>
      <main>
        <Outlet /> {/* alt rotalar burada render olur */}
      </main>
      <footer>Footer</footer>
    </div>
  );
}

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      { path: "about", element: <About /> },
      {
        path: "posts",
        element: <PostsLayout />,
        children: [
          { index: true, element: <PostList /> },
          { path: ":postId", element: <PostDetail /> },
        ],
      },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

8.2) Nested Routes (Ic Ice Rotalar)

jsx
function PostsLayout() {
  return (
    <div>
      <h2>Yazilar</h2>
      <Outlet />
    </div>
  );
}

function PostDetail() {
  const { postId } = useParams();
  return <p>Yazi ID: {postId}</p>;
}

8.3) Protected Routes (Korumali Rotalar)

jsx
import { Navigate, Outlet, useLocation } from "react-router-dom";

function ProtectedRoute() {
  const { user } = useAuth();
  const location = useLocation();

  if (!user) {
    // login sonrasi geri donmek icin konumu sakla
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return <Outlet />;
}

// Router'da kullanim
const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      { index: true, element: <Home /> },
      { path: "login", element: <Login /> },
      {
        element: <ProtectedRoute />, // path yok, sadece guard
        children: [
          { path: "dashboard", element: <Dashboard /> },
          { path: "profile", element: <Profile /> },
          { path: "settings", element: <Settings /> },
        ],
      },
    ],
  },
]);

// Login bilesen icinde geri yonlendirme
function Login() {
  const location = useLocation();
  const navigate = useNavigate();
  const from = location.state?.from?.pathname || "/dashboard";

  function handleLogin() {
    // login islemi
    navigate(from, { replace: true });
  }

  return <form>{/* ... */}</form>;
}

8.4) Lazy Loading Routes

jsx
import { lazy, Suspense } from "react";

const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      { index: true, element: <Home /> },
      {
        path: "dashboard",
        element: (
          <Suspense fallback={<p>Yukleniyor...</p>}>
            <Dashboard />
          </Suspense>
        ),
      },
      {
        path: "settings",
        element: (
          <Suspense fallback={<p>Yukleniyor...</p>}>
            <Settings />
          </Suspense>
        ),
      },
    ],
  },
]);

Toplu Suspense ile: Rotalari tek bir Suspense ile sarmalamak da mumkundur:

jsx
function SuspenseLayout() {
  return (
    <Suspense fallback={<FullPageSpinner />}>
      <Outlet />
    </Suspense>
  );
}

9) Forms & Validation

9.1) Kontrollu Input (Temel)

jsx
function LoginForm() {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");
  function handleSubmit(e) {
    e.preventDefault();
    // validate + submit
  }
  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button>Login</button>
    </form>
  );
}

9.2) React Hook Form + Zod Schema Validation

bash
npm i react-hook-form zod @hookform/resolvers

Detayli örnek:

jsx
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";

const registerSchema = z
  .object({
    name: z.string().min(2, "En az 2 karakter olmali"),
    email: z.string().email("Gecerli bir e-posta girin"),
    password: z
      .string()
      .min(8, "En az 8 karakter")
      .regex(/[A-Z]/, "En az bir buyuk harf")
      .regex(/[0-9]/, "En az bir rakam"),
    confirmPassword: z.string(),
    terms: z.literal(true, {
      errorMap: () => ({ message: "Kullanim sartlarini kabul edin" }),
    }),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: "Sifreler eslesmiyor",
    path: ["confirmPassword"],
  });

function RegisterForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    reset,
  } = useForm({
    resolver: zodResolver(registerSchema),
    defaultValues: {
      name: "",
      email: "",
      password: "",
      confirmPassword: "",
      terms: false,
    },
  });

  async function onSubmit(data) {
    await fetch("/api/register", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
    reset();
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} noValidate>
      <div>
        <label>Ad</label>
        <input {...register("name")} />
        {errors.name && <small>{errors.name.message}</small>}
      </div>

      <div>
        <label>E-posta</label>
        <input type="email" {...register("email")} />
        {errors.email && <small>{errors.email.message}</small>}
      </div>

      <div>
        <label>Sifre</label>
        <input type="password" {...register("password")} />
        {errors.password && <small>{errors.password.message}</small>}
      </div>

      <div>
        <label>Sifre Tekrar</label>
        <input type="password" {...register("confirmPassword")} />
        {errors.confirmPassword && (
          <small>{errors.confirmPassword.message}</small>
        )}
      </div>

      <div>
        <label>
          <input type="checkbox" {...register("terms")} />
          Kullanim sartlarini kabul ediyorum
        </label>
        {errors.terms && <small>{errors.terms.message}</small>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Gonderiliyor..." : "Kayit Ol"}
      </button>
    </form>
  );
}

React Hook Form avantajlari:

  • Uncontrolled yaklasim sayesinde minimum re-render
  • watch, setValue, getValues ile esnek kontrol
  • useFieldArray ile dinamik form alanlari
  • Schema-first validation ile tip guvenligi

10) Data Fetching: TanStack Query

10.1) Kurulum ve Setup

bash
npm i @tanstack/react-query
jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 dakika
      gcTime: 10 * 60 * 1000, // 10 dakika (eski cacheTime)
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

function Root() {
  return (
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

10.2) useQuery

jsx
import { useQuery } from "@tanstack/react-query";

// API fonksiyonlarini ayri dosyada tut
async function fetchPosts(page = 1) {
  const res = await fetch(`/api/posts?page=${page}`);
  if (!res.ok) throw new Error("Posts fetch failed");
  return res.json();
}

function PostList() {
  const [page, setPage] = useState(1);

  const { data, isLoading, isError, error, isFetching } = useQuery({
    queryKey: ["posts", page], // page degisince yeniden fetch
    queryFn: () => fetchPosts(page),
    placeholderData: (previousData) => previousData, // sayfa geciisinde onceki veriyi goster
  });

  if (isLoading) return <p>Ilk yukleme...</p>;
  if (isError) return <p>Hata: {error.message}</p>;

  return (
    <div>
      {isFetching && <span>Guncelleniyor...</span>}
      <ul>
        {data.posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
      <button onClick={() => setPage((p) => p - 1)} disabled={page === 1}>
        Onceki
      </button>
      <button onClick={() => setPage((p) => p + 1)}>Sonraki</button>
    </div>
  );
}

10.3) useMutation

jsx
import { useMutation, useQueryClient } from "@tanstack/react-query";

function CreatePost() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: async (newPost) => {
      const res = await fetch("/api/posts", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(newPost),
      });
      if (!res.ok) throw new Error("Create failed");
      return res.json();
    },
    onSuccess: () => {
      // posts sorgularini gecersiz kil -> otomatik yeniden fetch
      queryClient.invalidateQueries({ queryKey: ["posts"] });
    },
    onError: (error) => {
      alert("Hata: " + error.message);
    },
  });

  function handleSubmit(e) {
    e.preventDefault();
    const formData = new FormData(e.target);
    mutation.mutate({
      title: formData.get("title"),
      body: formData.get("body"),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="title" placeholder="Baslik" required />
      <textarea name="body" placeholder="Icerik" required />
      <button disabled={mutation.isPending}>
        {mutation.isPending ? "Kaydediliyor..." : "Kaydet"}
      </button>
      {mutation.isError && <p>Hata: {mutation.error.message}</p>}
    </form>
  );
}

10.4) Infinite Scroll

jsx
import { useInfiniteQuery } from "@tanstack/react-query";
import { useEffect, useRef } from "react";

async function fetchPostsPage({ pageParam = 1 }) {
  const res = await fetch(`/api/posts?page=${pageParam}&limit=20`);
  return res.json();
}

function InfinitePostList() {
  const observerRef = useRef(null);

  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
    isLoading,
  } = useInfiniteQuery({
    queryKey: ["posts", "infinite"],
    queryFn: fetchPostsPage,
    getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined,
    initialPageParam: 1,
  });

  // Intersection Observer ile otomatik yukleme
  useEffect(() => {
    if (!observerRef.current) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { threshold: 0.5 }
    );

    observer.observe(observerRef.current);
    return () => observer.disconnect();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  if (isLoading) return <p>Yukleniyor...</p>;

  return (
    <div>
      {data.pages.map((page, i) => (
        <div key={i}>
          {page.posts.map((post) => (
            <article key={post.id}>
              <h3>{post.title}</h3>
              <p>{post.body}</p>
            </article>
          ))}
        </div>
      ))}
      <div ref={observerRef}>
        {isFetchingNextPage
          ? "Daha fazla yukleniyor..."
          : hasNextPage
            ? "Daha fazla icerik var"
            : "Tum icerik yuklendi"}
      </div>
    </div>
  );
}

RTK Query vs TanStack Query:

  • RTKQ: Redux ekosistemi icinde kalmak istersen.
  • TSQ: Redux'a ihtiyac duymadan server state'i uctan uca yonetmek istersen.

11) Performance & Optimization

11.1) React.memo

Bir bileseni yalnizca prop'lari degistiginde yeniden render eder.

jsx
import { memo } from "react";

const TodoItem = memo(function TodoItem({ todo, onToggle }) {
  console.log("TodoItem render:", todo.id);
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.done}
        onChange={() => onToggle(todo.id)}
      />
      {todo.text}
    </li>
  );
});

// Ozel karsilastirma fonksiyonu
const PriceDisplay = memo(
  function PriceDisplay({ price, currency }) {
    return (
      <span>
        {price.toFixed(2)} {currency}
      </span>
    );
  },
  (prevProps, nextProps) => {
    // true donerse render ATLANIR
    return (
      prevProps.price === nextProps.price &&
      prevProps.currency === nextProps.currency
    );
  }
);

11.2) Code Splitting & Lazy Loading

jsx
import { lazy, Suspense } from "react";

// Route-based splitting
const AdminPanel = lazy(() => import("./pages/AdminPanel"));
const Analytics = lazy(() => import("./pages/Analytics"));

// Component-based splitting
const HeavyChart = lazy(() => import("./components/HeavyChart"));

function Dashboard() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <button onClick={() => setShowChart(true)}>Grafigi Goster</button>
      {showChart && (
        <Suspense fallback={<div>Grafik yukleniyor...</div>}>
          <HeavyChart />
        </Suspense>
      )}
    </div>
  );
}

11.3) Suspense

jsx
import { Suspense } from "react";

function App() {
  return (
    <Suspense fallback={<FullPageSpinner />}>
      <Header />
      <Suspense fallback={<ContentSkeleton />}>
        <MainContent />
      </Suspense>
      <Suspense fallback={<SidebarSkeleton />}>
        <Sidebar />
      </Suspense>
    </Suspense>
  );
}

Ic ice Suspense boundary'leri ile her bolum bagimsiz olarak yuklenebilir. Ana header hemen gorulur, içerik ve sidebar bagimsiz skeleton gosterir.

11.4) React Profiler

jsx
import { Profiler } from "react";

function onRender(id, phase, actualDuration, baseDuration) {
  console.log(`[${id}] ${phase}: ${actualDuration.toFixed(2)}ms`);
}

function App() {
  return (
    <Profiler id="App" onRender={onRender}>
      <MainContent />
    </Profiler>
  );
}

React DevTools Profiler sekmesi ile gorsel olarak hangi bilesenlerin ne kadar sure harcadigini inceleyebilirsiniz.

11.5) Virtualization (Büyük Liste Optimizasyonu)

Binlerce satir içeren listelerde yalnizca gorunen elemanlari render etmek için virtualization kullanilir.

bash
npm i @tanstack/react-virtual
jsx
import { useVirtualizer } from "@tanstack/react-virtual";
import { useRef } from "react";

function VirtualList({ items }) {
  const parentRef = useRef(null);

  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50, // tahmini satir yuksekligi (px)
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: "500px", overflow: "auto" }}>
      <div
        style={{
          height: `${virtualizer.getTotalSize()}px`,
          position: "relative",
        }}
      >
        {virtualizer.getVirtualItems().map((virtualItem) => (
          <div
            key={virtualItem.key}
            style={{
              position: "absolute",
              top: 0,
              left: 0,
              width: "100%",
              height: `${virtualItem.size}px`,
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            {items[virtualItem.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

11.6) Re-render Optimizasyon Kontrol Listesi

  1. State'i colocation ile yakin tut: State'i kullanildigi yere yakin tutun, gereksiz yere ust bilesene kaldirmayin.
  2. children prop ile optimizasyon: Sarmalayan bileşen render oldugunda children yeniden olusturulmaz.
  3. Selector pattern: Zustand/Redux'ta sadece ihtiyac duyulan state'i secin.
  4. Pahali hesaplamalari useMemo ile sarmayin: Ancak gercekten pahali ise.
  5. key prop'u doğru kullanin: Listeler için stabil ve benzersiz key kullanin; index kullanmayin.
  6. Context'i bolumlendirin: Tek büyük context yerine amaca yonelik küçük context'ler olusturun.
jsx
// children prop optimizasyonu
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  // theme degistiginde children yeniden olusturulmaz
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

12) Error Boundaries

12.1) Class Component ile Error Boundary

React'te error boundary'ler yalnizca class component olarak yazılabilir. Render sirasindaki hataları yakalar; event handler ve asenkron kodlardaki hataları yakalamaz.

jsx
import React from "react";

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    // Hata raporlama servisine gonder
    console.error("ErrorBoundary caught:", error, errorInfo);
    // ornek: Sentry.captureException(error, { extra: errorInfo });
  }

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }
      return (
        <div role="alert">
          <h2>Bir hata olustu</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Tekrar Dene
          </button>
        </div>
      );
    }
    return this.props.children;
  }
}

// kullanim
function App() {
  return (
    <ErrorBoundary>
      <Header />
      <ErrorBoundary fallback={<p>Widget yuklenemedi.</p>}>
        <RiskyWidget />
      </ErrorBoundary>
      <Footer />
    </ErrorBoundary>
  );
}

12.2) react-error-boundary Kutuphanesi

Daha modern ve fonksiyonel bir yaklasim için react-error-boundary kutuphanesi kullanılabilir.

bash
npm i react-error-boundary
jsx
import { ErrorBoundary, useErrorBoundary } from "react-error-boundary";

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h2>Bir seyler yanlis gitti</h2>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Tekrar Dene</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onReset={() => {
        // state sifirla, cache temizle vb.
      }}
      onError={(error, info) => {
        // hata logla
        console.error(error, info);
      }}
    >
      <MainApp />
    </ErrorBoundary>
  );
}

// Programatik hata firlatma
function DataLoader() {
  const { showBoundary } = useErrorBoundary();

  async function loadData() {
    try {
      const res = await fetch("/api/data");
      if (!res.ok) throw new Error("Veri yuklenemedi");
      const data = await res.json();
      setData(data);
    } catch (error) {
      showBoundary(error); // en yakin ErrorBoundary'e gonder
    }
  }

  return <button onClick={loadData}>Veri Yukle</button>;
}

13) Güvenlik (Security)

13.1) XSS ve dangerouslySetInnerHTML

React varsayılan olarak render edilen degerleri escape eder, bu XSS saldirilarinin cogunlugunu onler. Ancak dangerouslySetInnerHTML kullanirsaniz bu koruma devre disi kalir.

jsx
// GUVENLI: React otomatik escape eder
function SafeComponent({ userInput }) {
  return <p>{userInput}</p>; // <script> tag'i metin olarak gorulur
}

// TEHLIKELI: Ham HTML render eder
function DangerousComponent({ htmlContent }) {
  return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}

DOMPurify ile sanitization:

Eger dangerouslySetInnerHTML kullanmaniz gerekiyorsa (ornegin CMS'ten gelen zengin içerik), DOMPurify ile temizleyin.

bash
npm i dompurify
jsx
import DOMPurify from "dompurify";

function SafeHtml({ dirty }) {
  const clean = DOMPurify.sanitize(dirty, {
    ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br", "ul", "li"],
    ALLOWED_ATTR: ["href", "target"],
  });

  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

13.2) CSRF Korunmasi

SPA'larda CSRF token genellikle cookie veya header uzerinden yonetilir.

jsx
// Axios ile CSRF token otomatik ekleme
import axios from "axios";

const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE,
  withCredentials: true, // cookie gonderir
});

// CSRF token'i meta tag'den veya cookie'den oku
api.interceptors.request.use((config) => {
  const token = document.querySelector('meta[name="csrf-token"]')?.content;
  if (token) {
    config.headers["X-CSRF-TOKEN"] = token;
  }
  return config;
});

13.3) JWT Saklama

Saklama yeri       | XSS riski | CSRF riski | Onerilen?
-------------------|-----------|------------|----------
localStorage       | Yuksek    | Yok        | Hayir
sessionStorage     | Yuksek    | Yok        | Hayir
HttpOnly Cookie    | Yok       | Var*       | Evet
Memory (degisken)  | Dusuk     | Yok        | Kisa sureli

*CSRF riski SameSite=Strict cookie ile azaltilabilir.

En güvenli yaklasim: Access token'i memory'de, refresh token'i HttpOnly cookie'de tut.

jsx
// Token yonetimi ornegi
let accessToken = null;

export function setAccessToken(token) {
  accessToken = token;
}

export function getAccessToken() {
  return accessToken;
}

// Axios interceptor ile otomatik token ekleme
api.interceptors.request.use((config) => {
  const token = getAccessToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Token suresi doldugunda yenileme
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    if (error.response?.status === 401) {
      try {
        const res = await api.post("/auth/refresh");
        setAccessToken(res.data.accessToken);
        // orjinal istegi tekrarla
        error.config.headers.Authorization = `Bearer ${res.data.accessToken}`;
        return api(error.config);
      } catch {
        // refresh da basarisiz -> logout
        window.location.href = "/login";
      }
    }
    return Promise.reject(error);
  }
);

13.4) npm audit ve Bagimlilk Guvenligi

bash
# Guvenlik acigi taramasi
npm audit

# Otomatik duzeltme
npm audit fix

# Ciddi aciklar icin
npm audit fix --force

# Alternatif: snyk
npx snyk test

Genel güvenlik kontrol listesi:

  • Kullanici girdilerini her zaman dogrulayin (istemci VE sunucu)
  • dangerouslySetInnerHTML kullaniyorsaniz DOMPurify ile temizleyin
  • JWT'yi HttpOnly cookie'de saklayin
  • HTTPS kullanin
  • CORS'u doğru yapilandirin
  • Hassas verileri .env dosyasinda tutun, commit etmeyin
  • Duzgun olarak npm audit calistirin

14) RTK Query (Server State + Caching)

RTK'nin resmi data fetching cozumu.

jsx
// src/store/services/api.js
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: import.meta.env.VITE_API_BASE }),
  tagTypes: ['Post', 'User'],
  endpoints: (builder) => ({
    getPosts: builder.query({
      query: () => '/posts',
      providesTags: (res = []) =>
        res ? [...res.map(({ id }) => ({ type: 'Post', id })), { type: 'Post', id: 'LIST' }] : [{ type: 'Post', id: 'LIST' }],
    }),
    addPost: builder.mutation({
      query: (data) => ({ url: '/posts', method: 'POST', body: data }),
      invalidatesTags: [{ type: 'Post', id: 'LIST' }],
    }),
  }),
})
export const { useGetPostsQuery, useAddPostMutation } = api

Store'a ekleme:

jsx
import { configureStore } from '@reduxjs/toolkit'
import { api } from './services/api'
export const store = configureStore({
  reducer: { [api.reducerPath]: api.reducer },
  middleware: (gDM) => gDM().concat(api.middleware),
})

Kullanım:

jsx
function Posts() {
  const { data, isLoading, isError } = useGetPostsQuery()
  if (isLoading) return <p>Loading...</p>
  if (isError) return <p>Error</p>
  return data.map(p => <div key={p.id}>{p.title}</div>)
}

Summary: Built-in cache, invalidation, polling, re-fetch on focus.
(Artlar: Dahili cache, invalidation, odak/yeniden getirme.)


15) Styling (Tailwind + CSS Modules)

Tailwind setup (Hızlı stil):

bash
npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

tailwind.config.js -> content: ["./index.html","./src/**/*.{js,jsx}"]
index.css ->

css
@tailwind base;
@tailwind components;
@tailwind utilities;

CSS Modules:

jsx
import s from "./Button.module.css";
<button className={s.primary}>Click</button>

Summary: Tailwind for speed; CSS Modules for scoped styles.
(Hiz için Tailwind; kapsullenmis stiller için CSS Modules.)


16) Internationalization: react-i18next

bash
npm i react-i18next i18next
src/i18n/
 ├─ en/common.json
 └─ tr/common.json
jsx
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'
i18n.use(initReactI18next).init({
  resources: { en: { common: {/*...*/} }, tr: { common: {/*...*/} } },
  lng: 'en', fallbackLng: 'en', interpolation:{ escapeValue:false }
})

Kullanım:

jsx
import { useTranslation } from 'react-i18next'
const { t, i18n } = useTranslation('common')
<button onClick={()=>i18n.changeLanguage('tr')}>{t('changeLanguage')}</button>

Summary: Namespaces, lazy load, ICU format support.
(Namespace, tembel yükleme, ICU destegi.)


17) Animation: Framer Motion

bash
npm i framer-motion
jsx
import { motion } from 'framer-motion'
<motion.div initial={{opacity:0,y:8}} animate={{opacity:1,y:0}} transition={{duration:0.3}}>
  Hello Motion
</motion.div>

Summary: Micro interactions, layout animations, gestures.
(Mikro etkilesim, yerlesim animasyonlari, jestler.)


18) Testing (Vitest + React Testing Library)

18.1) Kurulum

bash
npm i -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom

vite.config.js test alanini ekle:

js
/// <reference types="vitest" />
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    setupFiles: "./src/test/setup.js",
    globals: true, // describe, it, expect global olarak kullanilabilir
    css: true,
  },
});

src/test/setup.js:

js
import "@testing-library/jest-dom";

18.2) Temel Test ve Screen Queries

jsx
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import Hello from "../Hello";

describe("Hello Component", () => {
  it("varsayilan isim ile render eder", () => {
    render(<Hello />);
    expect(screen.getByText(/Hello, World!/i)).toBeInTheDocument();
  });

  it("verilen isim ile render eder", () => {
    render(<Hello name="Fahri" />);
    expect(screen.getByText(/Hello, Fahri!/i)).toBeInTheDocument();
  });
});

Screen query tipleri:

Query tipi       | 0 sonuc  | 1 sonuc | 1+ sonuc | Asenkron?
-----------------|----------|---------|----------|----------
getBy...         | hata     | dondur  | hata     | Hayir
queryBy...       | null     | dondur  | hata     | Hayir
findBy...        | hata     | dondur  | hata     | Evet
getAllBy...       | hata     | dizi    | dizi     | Hayir
queryAllBy...    | []       | dizi    | dizi     | Hayir
findAllBy...     | hata     | dizi    | dizi     | Evet

Oncelik sirasi (en iyiden en kotusune):

  1. getByRole — erisilebilirlik rolu ile
  2. getByLabelText — form elementleri için
  3. getByPlaceholderText — placeholder ile
  4. getByText — gorunen metin ile
  5. getByTestId — son care olarak
jsx
// Ornek: cesitli query tipleri
render(<LoginForm />);

// role ile
const submitButton = screen.getByRole("button", { name: /giris/i });

// label ile
const emailInput = screen.getByLabelText(/e-posta/i);

// test-id ile (son care)
const logo = screen.getByTestId("app-logo");

18.3) userEvent ile Kullanici Etkilesimi

jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Counter from "../Counter";

describe("Counter", () => {
  it("butona tiklandiginda sayaci arttirir", async () => {
    const user = userEvent.setup();
    render(<Counter step={1} />);

    expect(screen.getByText("Count: 0")).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: /\+1/i }));
    expect(screen.getByText("Count: 1")).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: /\+1/i }));
    expect(screen.getByText("Count: 2")).toBeInTheDocument();
  });

  it("input'a metin girer", async () => {
    const user = userEvent.setup();
    render(<SearchBox />);

    const input = screen.getByPlaceholderText("Ara...");
    await user.type(input, "react");

    expect(input).toHaveValue("react");
  });
});

18.4) Custom Hook Testi

jsx
import { renderHook, act } from "@testing-library/react";
import { useCounter } from "../hooks/useCounter";

describe("useCounter", () => {
  it("baslangic degeriyle baslar", () => {
    const { result } = renderHook(() => useCounter(10));
    expect(result.current.count).toBe(10);
  });

  it("increment calisir", () => {
    const { result } = renderHook(() => useCounter(0));

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  it("hook argumani degistiginde guncellenir", () => {
    const { result, rerender } = renderHook(
      ({ initial }) => useCounter(initial),
      { initialProps: { initial: 0 } }
    );

    expect(result.current.count).toBe(0);

    rerender({ initial: 5 });
    // hook icindeki mantiga bagli olarak davranisi test edin
  });
});

18.5) MSW (Mock Service Worker) ile API Mocking

bash
npm i -D msw
js
// src/test/mocks/handlers.js
import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("/api/posts", () => {
    return HttpResponse.json([
      { id: 1, title: "Test Yazi 1" },
      { id: 2, title: "Test Yazi 2" },
    ]);
  }),

  http.post("/api/posts", async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: 3, ...body }, { status: 201 });
  }),

  http.get("/api/posts/:id", ({ params }) => {
    return HttpResponse.json({ id: params.id, title: `Yazi ${params.id}` });
  }),
];
js
// src/test/mocks/server.js
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);
js
// src/test/setup.js
import "@testing-library/jest-dom";
import { server } from "./mocks/server";
import { beforeAll, afterEach, afterAll } from "vitest";

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
jsx
// Test icinde kullanim
import { render, screen } from "@testing-library/react";
import { http, HttpResponse } from "msw";
import { server } from "../test/mocks/server";
import PostList from "../PostList";

describe("PostList", () => {
  it("postlari render eder", async () => {
    render(<PostList />);

    expect(await screen.findByText("Test Yazi 1")).toBeInTheDocument();
    expect(screen.getByText("Test Yazi 2")).toBeInTheDocument();
  });

  it("hata durumunu gosterir", async () => {
    // Bu test icin handler'i override et
    server.use(
      http.get("/api/posts", () => {
        return HttpResponse.json(null, { status: 500 });
      })
    );

    render(<PostList />);
    expect(await screen.findByText(/hata/i)).toBeInTheDocument();
  });
});

18.6) Snapshot Testing

jsx
import { render } from "@testing-library/react";
import { expect, it } from "vitest";
import Button from "../Button";

it("Button snapshot'a uyar", () => {
  const { container } = render(<Button variant="primary">Tikla</Button>);
  expect(container.firstChild).toMatchSnapshot();
});

// Inline snapshot (dosya olusturmadan)
it("Badge inline snapshot", () => {
  const { container } = render(<Badge count={5} />);
  expect(container.firstChild).toMatchInlineSnapshot(`
    <span class="badge">5</span>
  `);
});

Snapshot test ne zaman kullanilmali:

  • UI regresyon tespiti için
  • Uyari: Cok sik degisen bilesenler için kullanmayin, surekli güncelleme gerektirir

18.7) Coverage (Kapsam)

bash
# Coverage raporu olustur
npx vitest run --coverage

# Watch modunda
npx vitest --coverage

vite.config.js icine coverage ayarlari:

js
test: {
  coverage: {
    provider: "v8", // veya 'istanbul'
    reporter: ["text", "html", "lcov"],
    exclude: [
      "node_modules/",
      "src/test/",
      "**/*.d.ts",
      "**/*.config.*",
    ],
    thresholds: {
      statements: 80,
      branches: 80,
      functions: 80,
      lines: 80,
    },
  },
},

19) Async Data: fetch/axios + Patterns

Basic fetch (Temel kullanım):

jsx
function useFetch(url){
  const [data,setData] = React.useState(null);
  const [loading,setLoading] = React.useState(true);
  const [error,setError] = React.useState(null);
  React.useEffect(()=>{
    let on = true;
    (async () => {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error("Network error");
        const json = await res.json();
        if(on) setData(json);
      } catch (e) {
        if(on) setError(e);
      } finally {
        if(on) setLoading(false);
      }
    })();
    return () => { on = false };
  }, [url]);
  return { data, loading, error };
}

Patterns:

  • Loading -> Error -> Data states.
  • Retry logic (opsiyonel).
  • Cache with custom hook or libraries (TanStack Query).

Summary: Always handle loading & error explicitly.
(Loading ve error durumlarini mutlaka yonet.)


20) Tips & Best Practices

20.1) Component Boyutu

Bir bileşen 200 satirdan fazlaysa bolumlere ayirmayi dusunun. Ismi "ve" icerecek sekilde tanimlaniyorsa muhtemelen iki ayri bileşen olmalidir (ornegin: "HeaderAndNavigation" -> "Header" + "Navigation").

jsx
// Kotu: her sey bir bilesende
function UserPage() {
  // 300+ satir: form, tablo, modal, validation...
}

// Iyi: sorumluluk ayrilmis
function UserPage() {
  return (
    <div>
      <UserForm onSubmit={handleSubmit} />
      <UserTable users={users} />
      <DeleteUserModal />
    </div>
  );
}

20.2) Props Drilling Cozumleri

Props'u 3+ seviye derine gecirmek yerine su alternatifleri dusunun:

  1. Composition (children prop):
jsx
// Onceki: A -> B -> C -> D (prop drilling)
// Sonra: A children prop ile D'yi dogrudan gonderir
function Page({ user }) {
  return (
    <Layout>
      <Sidebar>
        <UserCard user={user} />
      </Sidebar>
    </Layout>
  );
}
  1. Context API: Tema, dil, auth gibi global degerler için
  2. State management: Zustand/Redux ile global state

20.3) key Prop Kullanimi

jsx
// YANLIS: index key olarak
{items.map((item, index) => (
  <ListItem key={index} item={item} />
))}
// Sorun: siralama degistiginde veya eleman silindiginde hatali davranis

// DOGRU: benzersiz ve stabil id
{items.map((item) => (
  <ListItem key={item.id} item={item} />
))}

// key ile bileseni sifirlamak (remount etmek)
<UserForm key={selectedUserId} user={selectedUser} />
// selectedUserId degistiginde form tamamen sifirlanir

20.4) useEffect Cleanup Kuralları

jsx
// Her zaman cleanup yapin:
// - setInterval / setTimeout
// - addEventListener
// - WebSocket baglantisi
// - AbortController
// - subscription (ornegin: Firebase onSnapshot)

React.useEffect(() => {
  const ws = new WebSocket("wss://api.example.com/ws");

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    setMessages((prev) => [...prev, data]);
  };

  return () => {
    ws.close(); // cleanup: baglantiyi kapat
  };
}, []);

20.5) Naming (Isimlendirme) Kurallari

Oge                  | Convention        | Ornek
---------------------|-------------------|---------------------------
Component            | PascalCase        | UserProfile, LoginForm
Hook                 | camelCase (use*)  | useAuth, useLocalStorage
Event handler        | handle*           | handleClick, handleSubmit
Boolean prop         | is/has/should     | isOpen, hasError, shouldFetch
Dosya (component)    | PascalCase        | UserProfile.jsx
Dosya (hook)         | camelCase         | useAuth.js
Dosya (util)         | camelCase         | formatDate.js
Sabit (constant)     | UPPER_SNAKE_CASE  | MAX_RETRY_COUNT, API_BASE_URL
Context              | PascalCase+Ctx    | AuthContext, ThemeContext

21) Project Structure & Env (.env)

Suggested structure (Öneri):

src/
 ├─ components/
 ├─ pages/
 ├─ hooks/
 ├─ contexts/
 ├─ services/    # api clients
 ├─ styles/
 ├─ lib/         # utils
 ├─ App.jsx
 └─ main.jsx

Environment variables:

  • Vite uses import.meta.env.VITE_*
    Create .env:
VITE_API_BASE_URL=https://api.example.com

Usage:

js
const base = import.meta.env.VITE_API_BASE_URL;

Summary: Keep structure predictable; centralize env config.
(Yapiyi ongorulebilir tut; ortam degiskenlerini merkezilestir.)


22) App Architecture & Foldering (Advanced)

src/
 ├─ app/            # providers (redux/query), global styles
 ├─ components/     # ui components
 ├─ features/       # domain slices (rtk) or modules
 ├─ hooks/          # reusable hooks
 ├─ pages/          # route-level components
 ├─ services/       # api clients (fetch/axios)
 ├─ stores/         # zustand/rtk stores
 ├─ i18n/           # locales & i18next init
 ├─ styles/         # tailwind / css modules
 └─ utils/          # helpers

Keep domains in features; colocate tests/styles with components.
(Domain'leri features altinda topla; test ve stilleri bilesenle birlikte konumlandir.)


23) Accessibility (A11y) & SEO Basics

  • Semantic HTML (button, nav, main).
  • Labels for inputs; aria-* when necessary.
  • Keyboard navigation; focus management.
  • Meta tags via index.html (Vite) or libraries (react-helmet-async).

Summary: A11y first; SEO basics via proper semantics.
(Erisilebilirligi one al; SEO için doğru semantik kullan.)


24) Developer Toolkit & Patterns

Tools: ESLint, Prettier, React DevTools, VSCode, Git, Husky + lint-staged.

Prettier + ESLint quick start:

bash
npm i -D eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks
npx eslint --init

Patterns:

  • Smart vs Dumb components (Container/Presentational)
  • Compound Components for flexible APIs
  • Headless UI approach (logic separate from presentation)
  • Error/Loading placeholders standardized
  • Render Props and Controlled/Uncontrolled patterns
  • Immutable updates; avoid deep prop drilling

Summary: Automate formatting; adopt consistent patterns.
(Formatlamayi otomatiklestir; tutarli kalipler benimse.)


25) Deployment (Vite build) & Hosting

Build (Üretim derlemesi):

bash
npm run build
npm run preview  # local preview

Outputs to dist/.

Static hosting options: Vercel, Netlify, GitHub Pages, DirectAdmin/cPanel (upload dist).
Server config: Ensure SPA fallback to index.html (rewrite 404 to /).

Summary: Build to dist/ and configure SPA rewrites.
(dist/ uretimini servis et; SPA rewrite kurali ekle.)


26) Debugging & DevTools

  • Redux DevTools for inspecting store state and action history.
  • React Query Devtools for cache status and query inspection.
  • React Profiler for render performance analysis.
  • Network tab to inspect API waterfalls and response times.

Summary: Use ecosystem-specific devtools alongside React Profiler.
(Ekosistem araclarini React Profiler ile birlikte kullan.)


27) Appendix: Snippets & Recipes

Toast system (zustand):

jsx
// useToast.js
import { create } from 'zustand'
export const useToast = create(set => ({
  toasts: [],
  push: (t) => set(s => ({ toasts: [...s.toasts, { id: crypto.randomUUID(), ...t }] })),
  remove: (id) => set(s => ({ toasts: s.toasts.filter(x => x.id !== id) })),
}))

Theme (dark/light) persisted:

jsx
function useTheme(){
  const [theme, setTheme] = React.useState(() => localStorage.getItem('theme') || 'light')
  React.useEffect(()=>{ document.documentElement.dataset.theme = theme; localStorage.setItem('theme', theme) },[theme])
  return { theme, setTheme }
}

Optimistic update (TanStack Query):

jsx
const mutation = useMutation({
  mutationFn: patchItem,
  onMutate: async (newItem) => {
    await qc.cancelQueries(['items'])
    const prev = qc.getQueryData(['items'])
    qc.setQueryData(['items'], old => old.map(i => i.id===newItem.id?{...i,...newItem}:i))
    return { prev }
  },
  onError: (_e,_v,ctx) => qc.setQueryData(['items'], ctx.prev),
  onSettled: () => qc.invalidateQueries(['items']),
})

28) Final Checklist (Son Kontrol Listesi)

  • [ ] Node 18+, deps up-to-date (Bagimliliklar guncel)
  • [ ] ESLint + Prettier configured
  • [ ] Components small & cohesive (Küçük ve odakli)
  • [ ] State colocated, minimal
  • [ ] Client state (RTK/Zustand) vs server state (RTKQ/TanStack) separated
  • [ ] Error boundaries for risky areas
  • [ ] Lazy load heavy routes/components
  • [ ] A11y checks & keyboard nav
  • [ ] .env via VITE_* & don't commit secrets
  • [ ] npm run build size acceptable; profile
  • [ ] SPA rewrites configured in hosting
  • [ ] Forms validated with schema (Zod/Yup)
  • [ ] i18n configured if multi-language needed
  • [ ] Security: XSS/CSRF onlemleri alinmis
  • [ ] JWT güvenli saklama stratejisi belirli
  • [ ] npm audit duzgun olarak calistiriliyor
  • [ ] Test coverage yeterli seviyede

Resources (Kaynaklar)

  • React Docs (Beta & Classic)
  • React Router Docs
  • Vite Docs
  • Redux Toolkit Docs
  • TanStack Query Docs
  • Zustand GitHub
  • React Hook Form Docs
  • MDN Web Docs
  • React Testing Library Docs

Congratulations -- you now have a production-ready understanding of React with Vite + JSX and the modern ecosystem!
(Tebrikler -- artik Vite + JSX ile React'te ve modern ekosistemde uretime hazir bilgi duzeyine sahipsin!)


Ilgili Rehberler

Frontend

Diger Kategoriler

Developer Guides & Technical References