📌 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
# Node 18+ onerilir
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run devVisit: http://localhost:5173 (Vite default).
File anatomy (Dosya yapisi):
src/
├─ assets/
├─ App.jsx
├─ main.jsx # React root
└─ index.cssHello Component (Merhaba Bileseni):
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)
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:
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.
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.
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.
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;
setCountsonrasicounthemen 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):
// 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.
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:
React.useEffect(() => {
const id = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(id);
}, []);Event listener ornegi:
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.
// 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.
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.
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.
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.
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.memoile 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:
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.
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):
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.
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.
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.
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:
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:
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:
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.
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):
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.
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.
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.
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.
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.
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
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}Keys: Unique & stable (id tercih edilir).
Conditional: &&, ternary, early returns.
{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.
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.
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 Toolkit7.3) Context API Detayli Örnek
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
npm i zustandimport { 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
npm i @reduxjs/toolkit react-reduxStore setup:
// 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:
// 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:
// 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
npm i react-router-domimport {
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)
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)
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
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:
function SuspenseLayout() {
return (
<Suspense fallback={<FullPageSpinner />}>
<Outlet />
</Suspense>
);
}9) Forms & Validation
9.1) Kontrollu Input (Temel)
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
npm i react-hook-form zod @hookform/resolversDetayli örnek:
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,getValuesile esnek kontroluseFieldArrayile dinamik form alanlari- Schema-first validation ile tip guvenligi
10) Data Fetching: TanStack Query
10.1) Kurulum ve Setup
npm i @tanstack/react-queryimport { 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
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
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
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.
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
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
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
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.
npm i @tanstack/react-virtualimport { 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
- State'i colocation ile yakin tut: State'i kullanildigi yere yakin tutun, gereksiz yere ust bilesene kaldirmayin.
- children prop ile optimizasyon: Sarmalayan bileşen render oldugunda children yeniden olusturulmaz.
- Selector pattern: Zustand/Redux'ta sadece ihtiyac duyulan state'i secin.
- Pahali hesaplamalari useMemo ile sarmayin: Ancak gercekten pahali ise.
- key prop'u doğru kullanin: Listeler için stabil ve benzersiz key kullanin; index kullanmayin.
- Context'i bolumlendirin: Tek büyük context yerine amaca yonelik küçük context'ler olusturun.
// 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.
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.
npm i react-error-boundaryimport { 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.
// 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.
npm i dompurifyimport 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.
// 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.
// 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
# Guvenlik acigi taramasi
npm audit
# Otomatik duzeltme
npm audit fix
# Ciddi aciklar icin
npm audit fix --force
# Alternatif: snyk
npx snyk testGenel güvenlik kontrol listesi:
- Kullanici girdilerini her zaman dogrulayin (istemci VE sunucu)
dangerouslySetInnerHTMLkullaniyorsaniz DOMPurify ile temizleyin- JWT'yi HttpOnly cookie'de saklayin
- HTTPS kullanin
- CORS'u doğru yapilandirin
- Hassas verileri
.envdosyasinda tutun, commit etmeyin - Duzgun olarak
npm auditcalistirin
14) RTK Query (Server State + Caching)
RTK'nin resmi data fetching cozumu.
// 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 } = apiStore'a ekleme:
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:
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):
npm i -D tailwindcss postcss autoprefixer
npx tailwindcss init -ptailwind.config.js -> content: ["./index.html","./src/**/*.{js,jsx}"]index.css ->
@tailwind base;
@tailwind components;
@tailwind utilities;CSS Modules:
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
npm i react-i18next i18nextsrc/i18n/
├─ en/common.json
└─ tr/common.jsonimport 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:
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
npm i framer-motionimport { 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
npm i -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdomvite.config.js test alanini ekle:
/// <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:
import "@testing-library/jest-dom";18.2) Temel Test ve Screen Queries
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 | EvetOncelik sirasi (en iyiden en kotusune):
getByRole— erisilebilirlik rolu ilegetByLabelText— form elementleri içingetByPlaceholderText— placeholder ilegetByText— gorunen metin ilegetByTestId— son care olarak
// 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
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
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
npm i -D msw// 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}` });
}),
];// src/test/mocks/server.js
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);// 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());// 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
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)
# Coverage raporu olustur
npx vitest run --coverage
# Watch modunda
npx vitest --coveragevite.config.js icine coverage ayarlari:
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):
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").
// 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:
- Composition (children prop):
// 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>
);
}- Context API: Tema, dil, auth gibi global degerler için
- State management: Zustand/Redux ile global state
20.3) key Prop Kullanimi
// 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 sifirlanir20.4) useEffect Cleanup Kuralları
// 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, ThemeContext21) Project Structure & Env (.env)
Suggested structure (Öneri):
src/
├─ components/
├─ pages/
├─ hooks/
├─ contexts/
├─ services/ # api clients
├─ styles/
├─ lib/ # utils
├─ App.jsx
└─ main.jsxEnvironment variables:
- Vite uses
import.meta.env.VITE_*
Create.env:
VITE_API_BASE_URL=https://api.example.comUsage:
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/ # helpersKeep 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:
npm i -D eslint prettier eslint-config-prettier eslint-plugin-react eslint-plugin-react-hooks
npx eslint --initPatterns:
- 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):
npm run build
npm run preview # local previewOutputs 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):
// 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:
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):
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
- [ ]
.envviaVITE_*& don't commit secrets - [ ]
npm run buildsize 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
- Frontend Genel Bakış
- JavaScript & TypeScript
- TypeScript Rehberi
- Next.js Rehberi
- Vue.js Rehberi
- CSS & Tailwind
- Web Performance
- Three.js Rehberi