"Öğrenecekleriniz" Paragrafının Açıklaması

Paylaştığınız metnin başındaki "Öğrenecekleriniz" (What you will learn) bölümü, bir yazılımcının tek başına modern bir web uygulamasını baştan sona nasıl inşa edeceğini özetlemektedir.


1. Giriş ve Kurulumlar (Intro & Setup)

Geliştirme ortamının hazırlanması ve temel araçların bilgisayara kurulması aşamasıdır.

IDE (Geliştirme Ortamı)

Kodlarımızı rahatça yazmak, renklendirmek ve hataları görmek için kullanılan editörlerdir. En popüleri Visual Studio Code (VS Code) aracıdır.

GitHub Kaynakları

Yazdığımız kodların yedeğini tutmak, geçmişe dönük değişiklikleri izlemek ve başkalarıyla paylaşmak için Git sürüm kontrol sistemini ve GitHub bulut platformunu kullanırız.

Node.js Kurulumu

JavaScript normalde sadece tarayıcıda çalışır. Node.js, JavaScript kodlarının bilgisayarımızda veya sunucuda doğrudan çalışmasını sağlayan bir çalışma ortamıdır. Sunucu yazmak ve React paketlerini yönetmek için şarttır.


2. React Temelleri (React Basics)

React, web sitelerinin kullanıcı arayüzlerini (UI) yapboz parçaları gibi küçük parçalarla (bileşenler) birleştiren bir kütüphanedir.

React Uygulaması Oluşturma ve Proje Yapısı

Modern React uygulamaları kurmak için Vite ya da create-react-app gibi araçlar kullanılır. Komut satırına şu yazılır:

bash

npm create vite@latest my-app -- --template react

Kodu dikkatli kullanın.

Proje açıldığında karşımıza src (kodlarımızın durduğu klasör), public (statik varlıklar) ve package.json (bağımlılık listesi) yapıları gelir.

JSX Mantığı (JSX Behind the scenes)

JSX, JavaScript içinde doğrudan HTML benzeri kodlar yazmamızı sağlar. Arka planda bu kodlar standart JavaScript fonksiyon çağrılarına (React.createElement) dönüştürülür.

Bileşenleri Bağlama ve Props (Connecting Components & Props)

Bileşenler (Components), tekrar kullanılabilir kod bloklarıdır. Props, üst bileşenden (Parent) alt bileşene (Child) veri aktarmak için kullanılan salt okunur parametrelerdir.

Uygulama: Haber Kartı Bileşeni

jsx

// Alt Bileşen (NewsItem.jsx) - Kendisine gelen başlık ve detay verilerini ekrana basar.
function NewsItem(props) {
  return (
    <div style={{ border: "1px solid #ccc", padding: "10px", margin: "10px" }}>
      <h2>{props.title}</h2>
      <p>{props.detail}</p>
    </div>
  );
}

// Üst Bileşen (App.jsx) - Alt bileşene veri (props) gönderir.
export default function App() {
  return (
    <div>
      <h1>Günün Haberleri</h1>
      <NewsItem title="Yapay Zeka Gelişiyor" detail="React 19 yapay zeka entegrasyonu kolaylaştı." />
      <NewsItem title="Hava Durumu" detail="Yarın tüm yurtta güneşli bir hava bekleniyor." />
    </div>
  );
}

Kodu dikkatli kullanın.

Bileşen Durumu (Component State, Prev State & Complex State)

State (Durum), bir bileşenin hafızasıdır. Kullanıcı butonlara tıkladıkça veya yazı yazdıkça değişen verileri saklar. Eğer yeni durum eski duruma bağlıysa güvenli veri değişimi için fonksiyonel güncelleme (prevState) kullanılır.

Uygulama: Sayaç ve İsim Listesi (Complex State)

jsx

import { useState } from 'react';

export default function CounterApp() {
  const [count, setCount] = useState(0);
  const [user, setUser] = useState({ name: "Ahmet", roles: ["User"] });

  const arttir = () => {
    // prevState kullanımı
    setCount(prevCount => prevCount + 1);
  };

  const rolEkle = () => {
    // Karmaşık durum (Complex State) güncellerken eski verileri korumak için ... (spread) kullanılır.
    setUser(prevUser => ({
      ...prevUser,
      roles: [...prevUser.roles, "Admin"]
    }));
  };

  return (
    <div style={{ padding: "20px" }}>
      <p>Butona basılma sayısı: {count}</p>
      <button onClick={arttir}>Arttır</button>
      
      <hr />
      <h3>Kullanıcı Bilgileri</h3>
      <p>İsim: {user.name}</p>
      <p>Roller: {user.roles.join(', ')}</p>
      <button onClick={rolEkle}>Admin Rolü Ekle</button>
    </div>
  );
}

Kodu dikkatli kullanın.

Çocuktan Ebeveyne Veri Aktarımı (Child to Parent)

Bileşenlerde veri akışı yukarıdan aşağı doğrudur. Ancak alt bileşenden üst bileşene bilgi göndermek için, üst bileşen alt bileşene bir fonksiyonu props olarak geçirir. Alt bileşen bu fonksiyonu tetiklediğinde veri yukarı çıkmış olur.


3. React Kancaları (React Hooks)

Hooks, fonksiyonel bileşenlerin state ve diğer React özelliklerini kullanabilmesini sağlayan özel fonksiyonlardır.

useEffect ve Temizleme (Cleanup Effect)

useEffect, bileşen ekrana ilk geldiğinde, ekrandan gittiğinde veya belirli veriler değiştiğinde çalışan yan etki (API'den veri çekme, zamanlayıcı başlatma vb.) kancasıdır. Cleanup fonksiyonu ise bileşen kapandığında arkada açık kalan abonelikleri veya zamanlayıcıları sıfırlayarak hafıza sızıntılarını önler.

useRef

DOM elementlerine doğrudan erişmek veya bileşen yeniden tetiklendiğinde sıfırlanmayan ama güncellendiğinde de ekranı yenilemeyen değişkenler tutmak için kullanılır.

React Context (Bölüm 1, 2, 3)

Props'ları kat kat aşağı taşımak (Props Drilling) yerine, verileri tüm bileşenlerin doğrudan erişebileceği global bir havuzda toplama yöntemidir.

Uygulama: Tema Değiştirici (Context & useEffect Application)

jsx

import { createContext, useState, useEffect, useContext } from 'react';

// 1. Kutu (Context) oluşturulur
const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");

  // useEffect ile tema değişimini izleme
  useEffect(() => {
    console.log(`Tema ${theme} olarak değiştirildi.`);
  }, [theme]);

  const toggleTheme = () => {
    setTheme(prev => prev === "light" ? "dark" : "light");
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// Tüketici alt bileşen
function ThemeButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <button onClick={toggleTheme}>
      Şu anki Tema: {theme}. Değiştirmek için tıkla.
    </button>
  );
}

export default function App() {
  return (
    <ThemeProvider>
      <div style={{ padding: "20px" }}>
        <h1>Context Uygulaması</h1>
        <ThemeButton />
      </div>
    </ThemeProvider>
  );
}

Kodu dikkatli kullanın.


4. Pratik Proje 1 - Hesabı Kim Öder? (Who pays the bill?)

Bu proje; state, validation (doğrulama), listeleme ve Context yapılarının harmanlandığı pratik bir uygulamadır.

Uygulama: Şanslı Kişiyi Seçme Projesi

jsx

import { useState } from 'react';

export default function WhoPaysApp() {
  const [players, setPlayers] = useState([]);
  const [input, setInput] = useState("");
  const [error, setError] = useState("");
  const [loser, setLoser] = useState("");

  const oyuncuEkle = (e) => {
    e.preventDefault();
    if (input.trim() === "") {
      setError("İsim boş bırakılamaz!");
      return;
    }
    setPlayers([...players, input]);
    setInput("");
    setError("");
  };

  const oyuncuSil = (index) => {
    const yeniListe = players.filter((_, i) => i !== index);
    setPlayers(yeniListe);
  };

  const secici = () => {
    if (players.length < 2) {
      setError("En az 2 oyuncu olmalıdır!");
      return;
    }
    const rastgeleIndeks = Math.floor(Math.random() * players.length);
    setLoser(players[rastgeleIndeks]);
  };

  return (
    <div style={{ padding: "20px", maxWidth: "400px", margin: "auto" }}>
      <h2>Hesabı Kim Öder?</h2>
      <form onSubmit={oyuncuEkle}>
        <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="İsim yazın" />
        <button type="submit">Ekle</button>
      </form>
      {error && <p style={{ color: "red" }}>{error}</p>}

      <ul>
        {players.map((p, idx) => (
          <li key={idx}>
            {p} <button onClick={() => oyuncuSil(idx)}>Sil</button>
          </li>
        ))}
      </ul>

      <button onClick={secici} style={{ marginTop: "10px", background: "orange" }}>Hesabı Ödeyecek Kişiyi Seç!</button>
      {loser && <h3 style={{ color: "darkred", marginTop: "15px" }}>Fatura kesilen şanssız: {loser} 💸</h3>}
    </div>
  );
}

Kodu dikkatli kullanın.


5. React Yönlendirici (React Router)

Çok sayfalı web siteleri hissi yaratmak amacıyla, tarayıcıyı tamamen yenilemeden sadece ilgili bileşenleri ekrana getiren sistemdir.


6. React Geçişleri ve Form Yönetimi (Transitions & Forms)

Animasyonlar ve Geçiş Grubu (Transition Group)

Bileşenler ekrana girerken veya çıkarken akıcı animasyonlar (solma, kayma) göstermek amacıyla CSS sınıflarını otomatik yöneten yapılardır.

Formik ve Şema Doğrulama (Formik & Validation Schema)

Büyük formlarda verileri toplamak, hata mesajlarını yönetmek ve form gönderim durumlarını kontrol etmek zordur. Formik bu süreçleri kısaltır. Form verilerinin doğruluğunu (eposta formatı, şifre uzunluğu vb.) kontrol etmek için ise Yup kütüphanesiyle şemalar oluşturulur.

Uygulama: Giriş Formu Örneği (Konsept Tasarımı)

jsx

// Formik ile entegre Yup şeması mantığı:
// const LoginSchema = Yup.object().shape({
//   email: Yup.string().email('Geçersiz E-posta').required('Zorunlu Alan'),
//   password: Yup.string().min(6, 'En az 6 karakter!').required('Zorunlu Alan')
// });

Kodu dikkatli kullanın.


7. React Karmaşık Durum Yönetimi (React Complex State & Redux)

Uygulama büyüdükçe Context yavaş kalabilir. Verileri yönetmek için daha profesyonel araçlara ihtiyaç duyulur.


8. Pratik Proje 2 - Günlük Haberler (Daily News)

Bu proje; Redux Toolkit, sahte bir API sunucusu (json-server) ve çok sayfalı yönlendirmelerin bir araya getirilmesiyle hazırlanan gelişmiş bir ön yüz portalıdır. Veriler sunucudan çekilir (fetching), ana sayfada kartlar halinde listelenir ve "daha fazla yükle" (load more) butonuyla sayfalandırma yapılır.


9. Next.js Temelleri ve İşleyişi (Next JS Basics)

Next.js, React tabanlı modern bir framework'tür (çerçeve). React kütüphanesine ek olarak dosya tabanlı yönlendirme, optimizasyon ve sunucu tarafı çözümleri sunar.

Dosya Tabanlı Rotalama (File-based Routing)

Next.js'te rota tanımlamak için harici kütüphanelere gerek yoktur. app klasörü içindeki klasör isimleri otomatik olarak web sitesinin link adresi olur.

Özel Rota Yapıları


10. Next.js Sunucu Tarafı İşlemleri (Next JS Rendering & Caching)

Next.js bileşenleri varsayılan olarak Server Components (Sunucu Bileşenleri) mimarisindedir. Kodlar sunucuda çalışır ve tarayıcıya sadece hazır HTML gönderilir.

Neden Kancaları (Hooks) Kullanamıyorum? (Server vs Client)

Sunucu bileşenlerinde useState veya useEffect gibi tarayıcıya özel kancalar çalışmaz; çünkü bu kodlar tarayıcıya hiç uğramadan sunucuda derlenir. Eğer butona tıklama gibi etkileşimler gerekiyorsa, dosyanın en üstüne "use client" yazılarak Client Component (İstemci Bileşeni) moduna geçilmelidir.

Sunucu Eylemleri (Server Actions) ve Form Yönetimi (useActionState)

Form verilerini işlemek için harici API uçları yazmaya gerek kalmadan, doğrudan sunucuda çalışan asenkron fonksiyonlar yazılabilir. useActionState kancası, bu sunucu eylemlerinin yüklenme ve hata durumlarını form içinde yönetmeyi kolaylaştırır.

Caching (Önbelleğe Alma) ve Veri Yenileme (Revalidate)

Next.js sunucuyu yormamak için veri isteklerini önbelleğe alır. revalidate seçeneği ile verilerin belirli saniyelerde bir veya el ile (on-demand) ne zaman güncelleneceği yapılandırılır.


11. Next.js API Rotaları, Arayüz ve Kimlik Doğrulama (API, UI & Auth)

API Rotaları (App Route Handlers)

Next.js sadece ön yüz değil, arka yüz (backend) hizmeti de verir. route.js dosyaları oluşturarak dış dünyaya GET, POST, PATCH, DELETE yanıtları veren HTTP servisleri yazılabilir.

Tailwind CSS ve Hero UI Entegrasyonu

Modern CSS framework'ü olan Tailwind CSS ile hızlıca responsive tasarımlar üretilir. Hazır bileşen kütüphaneleri (eski adıyla NextUI olan Hero UI) sayesinde profesyonel butonlar, tablolar ve pencereler projeye dahil edilir.

Next Auth Kimlik Doğrulama

Uygulamaya güvenli giriş-çıkış sistemleri eklemek için kullanılır. Kullanıcıların e-posta/şifre kombinasyonu ile veya GitHub, Google gibi platformlar aracılığıyla tek tıkla giriş yapmasını (OAuth) organize eder. Sunucu ve istemci tarafında oturum yönetimini (Session) kontrol altında tutar.


12. Pratik Proje 3 - Event0 (Etkinlik Yönetim Portalı)

Bu aşamada öğrenilen Next.js, Mongoose/MongoDB veritabanı, form doğrulamaları ve görsel bileşenler birleştirilerek tam teşekküllü bir Etkinlik Paylaşım Sitesi kurulur. Kullanıcılar sisteme giriş yapar, etkinlik mekanı ve tarihi belirleyerek yeni organizasyonlar yayınlar ve bu ilanlar ana sayfada listelenir.


13. Node.js Temelleri (Node JS Basics)

Node.js, asenkron ve olay güdümlü (event-driven) bir mimariye sahiptir.


14. Node Sunucusu ve Express Framework (Node Server & Express)

Node.js'in yalın haliyle sunucu yazmak çok satır kod gerektirir. Bu süreci basitleştirmek için Express.js minimal web çatısı kullanılır.

İstek ve Cevap Mekanizması, Ara Yazılımlar (Middleware)

Gelen isteklere (Request) karşılık HTML veya JSON verileri (Response) dönülür. Middleware, istek sunucuya ulaştıktan sonra asıl fonksiyona gitmeden önce araya girip kontrol yapan (örneğin giriş yapılmış mı, gelen veri düzgün mü) yapılardır.

Uygulama: Temel Express API Sunucusu (MongoDB Bağlantılı)

javascript

const express = require('express');
const app = express();

// Gelen JSON verilerini okuyabilmek için body-parser ara yazılımı aktifleştirilir
app.use(express.json());

// Geçici sahte veritabanı
let urunler = [
  { id: 1, ad: "Laptop" },
  { id: 2, ad: "Telefon" }
];

// GET İsteği - Verileri Getirme
app.get('/api/urunler', (req, res) => {
  res.json(urunler);
});

// POST İsteği - Yeni Veri Ekleme
app.post('/api/urunler', (req, res) => {
  const yeniUrun = { id: Date.now(), ad: req.body.ad };
  urunler.push(yeniUrun);
  res.status(201).json(yeniUrun);
});

// Sunucuyu Başlatma
app.listen(5000, () => {
  console.log("Express sunucusu port 5000 üzerinde çalışıyor.");
});

Kodu dikkatli kullanın.


15. Sunucu Tarafı Güvenlik ve Kimlik Doğrulama (Node Auth)

Şifrelerin Hashlenmesi (Hashing)

Kullanıcı şifreleri veritabanına asla düz metin olarak kaydedilmez. Güvenlik için bcrypt gibi kütüphanelerle geri dönüştürülemeyecek şekilde şifrelenir (hashlenir).

JWT (JSON Web Token)

Kullanıcı giriş yaptığında sunucu ona gizli bir anahtarla imzalanmış dijital bir jeton (token) verir. Kullanıcı sonraki isteklerinde bu jetonu sunucuya göndererek kimliğini kanıtlar, böylece sunucu her seferinde şifre sormak zorunda kalmaz.


16. Kapsamlı Full-Stack Proje - Flickbase (Arka Plan ve Ön Yüz)

Müfredatın son büyük projesi olan Flickbase, tam donanımlı bir içerik (makale/film) yönetim platformudur. Hem sunucu (backend) hem de istemci (frontend) tarafları sıfırdan inşa edilir.

Arka Plan Mimari Yapısı (Server Side)

Ön Yüz Entegrasyonu (Front End)




*************************************



Gönderdiğiniz ders müfredatını adım adım, satır satır inceleyip tasarladım. İstediğiniz gibi süreleri, önizleme yazılarını ve ders sayılarını tamamen temizledim. Sadece saf konulara, örneklere ve harika uygulamalara odaklandım.


🌟 "Öğrenecekleriniz" Bölümünün Açıklaması

Bu kurs, modern web siteleri ve uygulamaları yapmak için kullanılan en popüler araç olan React JS kütüphanesini öğretir. [1]


📚 Tüm Konuların Detaylı Açıklaması, Örnekleri ve Uygulamaları

🛠️ Temel Kurulumlar ve JavaScript Tekrarı

Set Up Development Environment & Configure VS Code

Kod yazabilmek için bilgisayarımıza bir laboratuvar kurmamız gerekir. VS Code isimli kod yazma programını indiririz ve işimizi kolaylaştıracak eklentiler yükleriz.

How Functions Work (Fonksiyonlar Nasıl Çalışır?)

Fonksiyonlar, bilgisayara verdiğimiz özel görevlerdir. Bir kez yazar, istediğimiz kadar kullanırız.

Find Method (Bulma Yöntemi)

Bir listenin içinden aradığımız tek bir şeyi şipşak bulmamızı sağlar.

Destructuring Arrays and Objects (Kutudan Çıkarma)

Büyük bir paketin içindeki eşyaları tek tek masaya dizmek gibidir. Nesne veya listelerin içindeki bilgileri kolayca dışarı alırız.

Rest & Spread Operators (Toplama ve Dağıtma - ...)

Üç nokta (...) işareti, bazen eldeki malzemeleri saçıp dağıtmaya (Spread), bazen de kalanları bir torbaya toplamaya (Rest) yarar.

Template Literals (Süslü Yazı Yazma)

Yazıların içine kolayca bilgisayar değişkenleri koymamızı sağlar. Ters tırnak (`) işaretiyle yapılır.

If/Else & Ternary Operator (Karar Verme Kısa Yolu)

Bilgisayara "Şöyleyse bunu yap, yoksa şunu yap" demektir. Ternary, bunu tek satırda yazar.

Logical Operators (Mantıksal Bağlaçlar: &&, ||, ??)

Kodların yan yana gelip şartları kontrol etmesidir. && (Ve), || (Veya) anlamına gelir.

Optional Chaining (?.)

Bir şeyin var olup olmadığından emin değilsek, kodumuzun hata verip çökmesini engeller. "Varsa bak, yoksa dur" der.

Map Method (Dönüştürme Yöntemi)

Bir listedeki tüm elemanları tek tek alıp, hepsini değiştirerek yeni bir liste yapar.

Filter Method (Süzgeç Yöntemi)

Bir listeden sadece bizim istediğimiz şartı sağlayanları süzer, gerisini eler.

Reduce Method (Kumbara Yöntemi)

Bir listedeki tüm elemanları toplayıp tek bir sonuç elde etmemizi sağlar.

Sort Method (Sıralama Yöntemi)

Karmakarışık bir listeyi düzgünce sıraya dizer.

Async JS – Promises & Async/Await (Beklemeli İşler)

İnternetten bir bilgi indirirken bilgisayarın donmaması için arka planda bekleme yapmasını sağlarız. Söz vermek (Promise) ve Beklemek (Await) kelimeleri kullanılır.


⚛️ React Temelleri, Parçalar (Props) ve Görünüm (JSX)

Creating a New React Project & Hello React!

Vite aracını kullanarak ilk boş React projemizi bilgisayarda çalıştırırız ve ekrana "Merhaba Dünya" yazarız.

Introduction to Components (Bileşenler)

React dünyasında her şey bir Lego parçası gibidir. Sitenin menüsü, logosu, her bir butonu ayrı birer bileşendir (Component). [4, 5]

How JSX Works & Conditional Rendering

JSX, JavaScript kodunun içine HTML (yani site tasarım dili) yazmamızı sağlar. Conditional Rendering ise duruma göre tasarım göstermektir. [6]

How Props Work (Özellik Taşımacılığı)

Bir Lego parçasına dışarıdan renk veya yazı bilgisi göndermektir. [4]


📊 Hafıza Yönetimi (useState & Olaylar)

Events in React (Tıklamalar ve Hareketler)

Kullanıcının siteyle etkileşime girmesidir. Butona tıklamak (onClick) gibi olayları yakalarız.

How useState Works (Değişen Hafıza)

Bir sayfanın o anki durumunu hafızada tutmasını sağlar. Sayı her değiştiğinde ekran kendiliğinden güncellenir. [1, 7]


📝 Proje #1: Görev Listesi (Task List)

Bu bölümde öğrendiklerimizle çalışan bir uygulama yaparız. Ekrandaki bir kutuya görev yazarız, ekle deyince listeye eklenir. Yapılan görevlerin üstünü çizebilir veya silebiliriz.


👶 İleri Düzey Props Özellikleri

How Children Prop Works (İç içe Koyma)

Bir bileşenin içine başka tasarımlar yerleştirmemizi sağlar. Kutu gibi davranır.


🌐 Dış Dünyadan Bilgi Alma (useEffect)

A First Look at useEffect (Arka Plan İşleri)

Sayfa ilk açıldığında internetten otomatik bilgi çekmek veya bir zamanlayıcı başlatmak için kullanılır.


💱 Proje #2: Döviz Çevirici Hesap Makinesi

Bu küçük projede useEffect kullanarak canlı döviz verileri çeken ve paraları birbirine çeviren bir hesap makinesi tasarlanır.


🧠 Önemli Teorik Bilgiler (Virtual DOM & Fiber Tree)

React'in siteleri neden bu kadar hızlı çalıştırdığını anlatan mutfak kısmıdır. React, gerçek siteyi değiştirmeden önce hafızada hayali bir kopyasını (Virtual DOM) hazırlar. Sadece değişen küçük parçayı ekrana yansıtır. [1]


💾 useRef & Özel Kod Yapıları (Custom Hooks)

useRef for Accessing Elements

Sayfadaki bir yazı kutusuna (input) tıklanmadan otomatik odaklanmayı (focus) sağlar. Değiştiğinde sayfayı baştan yenilemeyen gizli bir kutudur.

How to Create a Custom Hook

Kendi özel React aracımızı üretmektir. Kodları temiz tutmaya yarar.


🛣️ Sayfa Yönlendirici (React Router)

useParams & useSearchParams Hooks

Sitemizde binlerce ürün varsa, her ürün için ayrı sayfa tasarımı yapmayız. Tek bir sayfa yapar, ürünün numarasını (ID) adresten çekeriz.


🎨 Tasarım Sihirbazı (Tailwind CSS)

Setting Up Tailwind & Tailwind Sınıfları

Tasarım yapmak için CSS dosyasında satırlarca kod yazmak yerine hazır kelimeler (sınıflar) kullanırız.


⚡ Gelişmiş Router Özellikleri (Data Loading)

Sayfa daha ekrana gelmeden önce, internetten gelecek verilerin arka planda erkenden yüklenmesini sağlayan modern yöntemdir.


📡 Ortak Hafıza Sistemi (Context API)

Bileşenler çok derinleştikçe, en üstteki bilgiyi en alttakine taşımak zorlaşır. Context API, tüm uygulamanın etrafını saran ortak bir radyo yayını gibidir. İsteyen her bileşen bu yayına bağlanıp bilgiyi dinleyebilir.


🎛️ Büyük Değişim Kumandası (useReducer)

Uygulamadaki butonlar ve işlemler çok karmaşık hale geldiğinde (Örn: Ekle, Sil, Düzenle, Hepsini Temizle, Sırala), tüm bu emirleri tek bir büyük merkezden ("Switch-Case" yapısıyla) yönetmemizi sağlayan gelişmiş hafıza sistemidir.


📦 Dev Hafıza Deposu (Redux with Thunks & RTK Modern Redux)

Installing Redux Toolkit & Creating Store

Çok büyük bir alışveriş sitesi yaptığınızı düşünün. Kullanıcı giriş bilgisi, sepet listesi, gece modu ayarı gibi her şeyi dev bir ortak depoda (Store) saklarız. [3]

Loading Users with createAsyncThunk

İnternetten kullanıcı listesini çekerken "Yükleniyor", "Başarılı" veya "Hata Oluştu" gibi adımları Redux deposuna otomatik bildiren süper yardımcı bir araçtır. [3, 8]


🚀 Performans Hızlandırma (Optimization)

useMemo & useCallback & Lazy Loading

Sitenin boş yere yorulmasını engeller. Değişmeyen hesaplamaları hafızada tutar (useMemo). Sayfaları sadece kullanıcı tıkladığında parça parça indirir (Lazy Loading), böylece site ışık hızında açılır.


🌍 Projeyi Dünyaya Açma (Project Deployment)

Yazdığımız tüm kodları paketleyip (npm run build) internetteki ücretsiz sunuculara yükleriz. Böylece artık web sitemizin bir linki olur ve dünyadaki herkes sitemizi ziyaret edebilir.


================================================================================

Tüm müfredat adımlarını kapsayan, Tailwind CSS ile modern ve şık bir arayüz tasarımına sahip, tamamen çalışan dev bir React uygulaması hazırladım.

Bu tek parça kod içerisinde; JavaScript temellerinden useState ve useEffect'e, useRef ve Custom Hook kullanımından useReducer, Context API ve Redux mantığına kadar müfredattaki tüm kritik araçlar bir araya getirilmiştir.

Kodu bilgisayarınızda denemek için temiz bir React projesine Tailwind CSS kurduktan sonra direkt App.jsx dosyanıza yapıştırıp çalıştırabilirsiniz.

import React, { useState, useEffect, useReducer, useContext, createContext, useRef } from "react";

// ==========================================
// 1. CONTEXT API (Ortak Hafıza Sistemi)
// ==========================================
const TemaContext = createContext();

// ==========================================
// 2. USEREDUCER (Büyük Değişim Kumandası)
// ==========================================
// Görevleri ekleme, silme ve durumunu değiştirme işlemlerini yönetir
const gorevReducer = (state, action) => {
  switch (action.type) {
    case "EKLE":
      return [...state, { id: Date.now(), metin: action.payload, tamamlandi: false }];
    case "SIL":
      return state.filter((g) => g.id !== action.payload);
    case "DURUM_DEGISTIR":
      return state.map((g) =>
        g.id === action.payload ? { ...g, tamamlandi: !g.tamamlandi } : g
      );
    default:
      return state;
  }
};

// ==========================================
// 3. CUSTOM HOOK (Özel Kod Yapısı)
// ==========================================
// Döviz çevirici için internetten (simüle edilmiş) anlık veri çeken özel kanca
const useDovizKuru = (paraBirimi) => {
  const [oran, setOran] = useState(1);
  const [yukleniyor, setYukleniyor] = useState(false);

  useEffect(() => {
    setYukleniyor(true);
    // USEEFFECT & ASYNC/AWAIT: Arka planda internetten veri çekme taklidi
    const kuruGetir = async () => {
      await new Promise((resolve) => setTimeout(resolve, 800)); // Hızı görmek için bekleme
      const kurlar = { USD: 34.2, EUR: 36.8, GBP: 43.1 };
      setOran(kurlar[paraBirimi] || 1);
      setYukleniyor(false);
    };
    kuruGetir();
  }, [paraBirimi]); // Dependency Array: Para birimi değiştikçe yeniden çalışır

  return { oran, yukleniyor };
};

// ==========================================
// MAIN APP COMPONENT (Ana Uygulama)
// ==========================================
export default function App() {
  const [tema, setTema] = useState("light");

  return (
    <TemaContext.Provider value={{ tema, setTema }}>
      <div className={`${tema === "dark" ? "bg-gray-900 text-white" : "bg-gray-50 text-gray-800"} min-h-screen transition-colors duration-300 font-sans`}>
        
        {/* TAILWIND CSS: Şık ve modern üst menü tasarımı */}
        <header className="border-b border-gray-200 dark:border-gray-700 p-4 shadow-sm bg-white dark:bg-gray-800">
          <div className="max-w-5xl mx-auto flex justify-between items-center">
            <h1 className="text-2xl font-black bg-gradient-to-r from-blue-600 to-indigo-500 bg-clip-text text-transparent">
              🚀 React Master Dashboard
            </h1>
            <button
              onClick={() => setTema(tema === "light" ? "dark" : "light")}
              className="px-4 py-2 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg text-sm font-semibold transition-all shadow-sm"
            >
              {tema === "light" ? "🌙 Gece Modu" : "☀️ Gündüz Modu"}
            </button>
          </div>
        </header>

        {/* Ana İçerik Alanı */}
        <main className="max-w-5xl mx-auto p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
          
          {/* BÖLÜM 1: GÖREV LİSTESİ (useReducer & useRef Örneği) */}
          <section className="bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700">
            <h2 className="text-lg font-bold mb-4 flex items-center gap-2">
              📝 Görev Planlayıcı <span className="text-xs bg-blue-100 text-blue-800 px-2 py-0.5 rounded-full font-normal">useReducer</span>
            </h2>
            
            <div className="flex gap-2 mb-4">
              {/* USEREF: Kutunun içine otomatik odaklanmak veya doğrudan erişmek için */}
              <input
                ref={inputRef}
                type="text"
                placeholder="Yeni görev yazın..."
                className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl bg-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
                onKeyDown={(e) => e.key === "Enter" && gorevEkle()}
              />
              <button
                onClick={gorevEkle}
                className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-xl font-medium transition-all"
              >
                Ekle
              </button>
            </div>

            {/* JAVASCRIPT MAP & FILTER: Listeyi ekrana basma ve boşsa kontrolü */}
            {gorevler.length === 0 ? (
              <p className="text-sm text-gray-400 text-center py-4">Henüz bir görev eklenmedi.</p>
            ) : (
              <ul className="space-y-2">
                {gorevler.map((g) => (
                  <li
                    key={g.id}
                    className="flex justify-between items-center p-3 rounded-xl bg-gray-50 dark:bg-gray-700/50 group border border-gray-100 dark:border-gray-700"
                  >
                    <span
                      onClick={() => dispatch({ type: "DURUM_DEGISTIR", payload: g.id })}
                      className={`cursor-pointer flex-1 text-sm ${g.tamamlandi ? "line-through text-gray-400" : ""}`}
                    >
                      {g.metin}
                    </span>
                    <button
                      onClick={() => dispatch({ type: "SIL", payload: g.id })}
                      className="text-red-500 hover:text-red-700 opacity-0 group-hover:opacity-100 transition-opacity text-sm font-medium px-2"
                    >
                      Sil
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </section>

          {/* BÖLÜM 2: DÖVİZ HESAPLAYICI (Custom Hook & useEffect Örneği) */}
          <section className="bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 flex flex-col justify-between">
            <div>
              <h2 className="text-lg font-bold mb-4 flex items-center gap-2">
                💱 Canlı Döviz Çevirici <span className="text-xs bg-purple-100 text-purple-800 px-2 py-0.5 rounded-full font-normal">Custom Hook</span>
              </h2>

              {/* USESTATE: Girdi değerlerini ve seçilen para birimini hafızada tutar */}
              <div className="space-y-4">
                <div>
                  <label className="text-xs text-gray-400 block mb-1">Miktar (Döviz Tipi)</label>
                  <input
                    type="number"
                    value={miktar}
                    onChange={(e) => setMiktar(Number(e.target.value))}
                    className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl bg-transparent focus:outline-none focus:ring-2 focus:ring-purple-500"
                  />
                </div>

                <div>
                  <label className="text-xs text-gray-400 block mb-1">Para Birimi Seçin</label>
                  <select
                    value={paraBirimi}
                    onChange={(e) => setParaBirimi(e.target.value)}
                    className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-xl bg-white dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-purple-500"
                  >
                    <option value="USD">USD - Amerikan Doları</option>
                    <option value="EUR">EUR - Euro</option>
                    <option value="GBP">GBP - İngiliz Sterlini</option>
                  </select>
                </div>
              </div>
            </div>

            {/* Hesaplama Sonucu ve Yapay Yüklenme Alanı */}
            <div className="mt-6 p-4 rounded-xl bg-purple-50 dark:bg-purple-950/30 text-center border border-purple-100 dark:border-purple-900/50">
              {yukleniyor ? (
                <span className="text-sm text-purple-500 font-medium animate-pulse">Kurlar güncelleniyor...</span>
              ) : (
                <div>
                  <p className="text-xs text-purple-400">Türk Lirası Karşılığı</p>
                  <p className="text-2xl font-black text-purple-600 dark:text-purple-400">
                    {/* JAVASCRIPT REDUCE/MATH MANTIĞI: Hesaplama */}
                    {(miktar * oran).toFixed(2)} TL
                  </p>
                  <p className="text-[10px] text-gray-400 mt-1">1 {paraBirimi} = {oran} TL</p>
                </div>
              )}
            </div>
          </section>

          {/* BÖLÜM 3: MERKEZİ REDUX DEPOSU TAKLİDİ (Global State Mantığı) */}
          <section className="bg-white dark:bg-gray-800 p-6 rounded-2xl shadow-xl border border-gray-100 dark:border-gray-700 md:col-span-2">
            <h2 className="text-lg font-bold mb-2 flex items-center gap-2">
              📦 Global Mağaza Sepeti <span className="text-xs bg-emerald-100 text-emerald-800 px-2 py-0.5 rounded-full font-normal">Redux Toolkit Mantığı</span>
            </h2>
            <p className="text-xs text-gray-400 mb-4">
              Aşağıdaki sepet verileri, hiyerarşiden bağımsız olarak tüm uygulamayı besleyen "Redux Store" yapısını taklit eder.
            </p>

            <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
              {[
                { id: 1, ad: "Kablosuz Kulaklık", fiyat: 1200 },
                { id: 2, ad: "Mekanik Klavye", fiyat: 2400 },
                { id: 3, ad: "Oyuncu Mouse", fiyat: 850 },
              ].map((urun) => (
                <div key={urun.id} className="p-4 rounded-xl border border-gray-200 dark:border-gray-700 flex flex-col justify-between items-start bg-gray-50/50 dark:bg-gray-700/30">
                  <div>
                    <h4 className="font-semibold text-sm">{urun.ad}</h4>
                    <p className="text-xs text-emerald-600 font-bold mt-1">{urun.fiyat} TL</p>
                  </div>
                  <button
                    onClick={() => sepeteEkle(urun)}

className="mt-3 w-full bg-emerald-600 hover:bg-emerald-700 text-white text-xs py-2 rounded-lg font-semibold transition-all"
>
Sepete Ekle


))}

{/* Sepet Detayı ve Toplam Tutar (Reduce kullanımı) */}


Sepetteki Ürün Sayısı:{" "}
{sepet.length}


Toplam Tutar: {sepet.reduce((toplam, urun) => toplam + urun.fiyat, 0)} TL


</TemaContext.Provider>
);

// ==========================================
// BİLEŞEN İÇİ TANIMLAMALAR VE FONKSİYONLAR
// ==========================================
// Görev Listesi State'leri
const [gorevler, dispatch] = useReducer(gorevReducer, [
{ id: 1, metin: "React Router Kurulumunu İncele", tamamlandi: true },
{ id: 2, metin: "Tailwind Tasarımını Tamamla", tamamlandi: false },
]);
const inputRef = useRef(null);

function gorevEkle() {
const metin = inputRef.current.value.trim();
if (metin) {
dispatch({ type: "EKLE", payload: metin });
inputRef.current.value = ""; // Kutuyu temizle
}
}

// Döviz Çevirici State'leri
const [miktar, setMiktar] = useState(10);
const [paraBirimi, setParaBirimi] = useState("USD");
const { oran, yukleniyor } = useDovizKuru(paraBirimi); // Custom Hook'tan verileri çektik

// Sepet (Redux benzeri) State'i
const [sepet, setSepet] = useState([]);
function sepeteEkle(urun) {
setSepet([...sepet, urun]);
}
}

### 💎 Kodun İçindeki Müfredat Cevherleri Nelerdir?
1. **Görünüm Tasarımı (Tailwind CSS):** Renk geçişleri (`bg-gradient-to-r`), karanlık mod uyumu (`dark:bg-gray-900`), gölgeler (`shadow-xl`) ve oval köşeler (`rounded-2xl`) kullanılarak son derece profesyonel, göze hoş gelen bir arayüz oluşturulmuştur.
2. **Hafıza Yönetimi (`useReducer` & `useState`):** Görev listesindeki silme ve ekleme gibi karmaşık emir serileri tek merkezden `useReducer` ile kontrol edilirken, döviz miktar girdileri gibi anlık durumlar `useState` ile yönetilmiştir.
3. **Harici Bağlantı (`useEffect` & `Custom Hook`):** Yazılan `useDovizKuru` kancası sayesinde, internetten veri çekme esnasında kullanıcının ekranında beliren yüklenme efektleri (`animate-pulse`) asenkron (`async/await`) mimariyle simüle edilmiştir.
4. **Doğrudan Erişim (`useRef`):** Görev kutusuna girilen veriler `useRef` üzerinden okunmuş ve kutu işlem sonrası temizlenmiştir.

<FollowUp>
Eğer isterseniz projenin bu kısmına:
* Sayfalar arası geçişi canlandırmak için **React Router (sayfa yönlendirme) simülasyonu** ekleyebiliriz.
* Redux Toolkit'in orijinal **slice ve store kurulum kodlarını** entegre edebiliriz.

Uygulamayı tarayıcınızda çalıştırma konusunda bir sorun yaşadınız mı?
</FollowUp>

***********************************************************************************************


Bu uzun ve detaylı React/Redux kurs müfredatını sizin için adım adım, parça parça ele alacağız. Harika bir öğrenme rehberi oluşturmak için her bölümü derinlemesine inceleyecek, örnekler ve uygulamalarla zenginleştireceğiz.

İlk olarak en başından, yani sizin özellikle istediğiniz "Öğrenecekleriniz" kısmını açıklayarak başlayalım.


BÖLÜM 1: Giriş ve "Öğrenecekleriniz" Bölümünün Açıklaması

🎯 "Öğrenecekleriniz" Paragrafının Detaylı Açıklaması

Bu kurs, modern bir web yazılımcısı (Software Engineer) olmanız için gereken tüm modern React ekosistemini size sıfırdan öğretmeyi hedeflemektedir. Maddeler halinde ne anlama geldiğini açıklayalım:


BÖLÜM 2: JSX Dünyası ve Temel Bileşen Yapısı

Kursun ilk teknik parçası, React'in temel yapı taşı olan JSX ve Bileşen (Component) mantığıdır.

📐 Konu Anlatımı: JSX Nedir ve Nasıl Çalışır?

JSX, JavaScript içinde tıpkı HTML yazar gibi kod yazmamızı sağlayan özel bir sözdizimidir. Tarayıcılar JSX kodunu doğrudan anlayamaz. Bu yüzden Babel adlı bir araç, bizim yazdığımız HTML benzeri kodları arka planda gerçek JavaScript fonksiyonlarına (React.createElement) dönüştürür.

JSX Kuralları:

  1. Tek bir ana kapsayıcı olmalı: İki tane <div> yan yana doğrudan döndürülemez. Bir dış <div> veya Boş Etiket (React Fragment <> ... </>) içine alınmalıdır.

  2. Sınıf isimleri: HTML'deki class yerine className kullanılır.

  3. JavaScript çalıştırma: JSX içinde dinamik bir veri göstermek istiyorsak süslü parantez {} kullanırız.

💻 Kod Örneği: Temel Bir Bileşen Tasarımı

Bir kullanıcının adını ve o anki saati gösteren basit bir bileşen yapalım:

import React from 'react';

function ProfilBilgisi() {
  const kullaniciAdi = "Ahmet Yılmaz";
  const su AnkiSaat = new Date().toLocaleTimeString();
  const baslikStili = { color: 'blue', fontSize: '20px' };

  return (
    <div className="profil-karti">
      <h1 style={baslikStili}>Hoş Geldiniz, {kullaniciAdi}!</h1>
      <p>Sisteme giriş saatiniz: {suAnkiSaat}</p>
      <input type="text" autoFocus={true} placeholder="Not bırakın..." />
    </div>
  );
}

export default ProfilBilgisi;

🛠️ Uygulama: HTML Kodunu JSX'e Dönüştürme

Hatalı (Klasik HTML'den kopyalanan) yapı:

<!-- Bu kod React içinde doğrudan çalışmaz, hata verir -->
<div class="kart">
  <h2 style="color: red;">Ürün Açıklaması</h2>
  <input type="text" autofocus>
</div>

Doğru JSX Dönüşümü:

function UrunKarti() {
  return (
    <div className="kart">
      <h2 style={{ color: 'red' }}>Ürün Açıklaması</h2>
      <input type="text" autoFocus={true} />
    </div>
  );
}

Açıklama: class kelimesi className yapıldı. style özelliğine çift süslü parantez içinde bir JavaScript objesi verildi. autofocus ise camelCase kuralına göre autoFocus haline getirildi.


BÖLÜM 3: Props (Özellikler) Sistemi ile Reusable Bileşenler

Uygulamamız büyüdükçe aynı kodu tekrar tekrar yazmak yerine, kendisi sabit ama içeriği dışarıdan gelen verilere göre değişen "geliştirilebilir/tekrar kullanılabilir" yapılar kurarız. Bunlara Props denir.

📐 Konu Anlatımı: Props Mantığı ve Destructuring

Props, bir ana (Parent) bileşenden alt (Child) bileşene gönderilen bilgi paketleridir. Veri akışı her zaman yukarıdan aşağıya doğrudur. Alt bileşen bu veriyi değiştiremez, sadece okur.

JavaScript'teki Argument Destructuring (Argüman Parçalama) yöntemi sayesinde, props.baslik, props.detay yazmak yerine doğrudan { baslik, detay } şeklinde temiz bir kod yazabiliriz.

💻 Kod Örneği: Profil Kartı Uygulaması

Önce dışarıdan veri alan alt bileşenimizi (ProfileCard.jsx) tasarlayalım:

import React from 'react';

// Argument Destructuring kullanarak props'ları doğrudan aldık
function ProfileCard({ isim, kullaniciAdi, gorselLink, altMetin }) {
  return (
    <div className="card" style={{ border: '1px solid gray', padding: '10px', margin: '10px' }}>
      <img src={gorselLink} alt={altMetin} style={{ width: '100px', height: '100px' }} />
      <h3>{isim}</h3>
      <p>@{kullaniciAdi}</p>
    </div>
  );
}

export default ProfileCard;

Şimdi bu bileşeni ana bileşenimiz içinde farklı verilerle kullanalım (App.jsx):

import React from 'react';
import ProfileCard from './ProfileCard';

function App() {
  return (
    <div>
      <h2>Ekibimiz</h2>
      <ProfileCard 
        isim="Canan Dağdeviren" 
        kullaniciAdi="canand" 
        gorselLink="https://picsum.photos" 
        altMetin="Canan profil resmi"
      />
      <ProfileCard 
        isim="Aziz Sancar" 
        kullaniciAdi="asancar" 
        gorselLink="https://picsum.photos" 
        altMetin="Aziz profil resmi"
      />
    </div>
  );
}

export default App;

BÖLÜM 4: State (Durum) ve Event (Olay) Yönetimi

Uygulamanın kullanıcıyla etkileşime girdiği, ekranın değiştiği ve canlandığı yer burasıdır.

📐 Konu Anlatımı: State ve Re-Rendering Süreci

React'te normal değişkenler değiştiğinde ekran güncellenmez. Ekranın değişmesini (yeniden çizilmesini - re-render) istiyorsak State kullanmalıyız. State yönetimi için React ile birlikte gelen useState fonksiyonunu (Hook) kullanırız.

useState bize iki şey verir:

  1. Mevcut durumun değeri (count)

  2. Bu durumu değiştirecek özel fonksiyon (setCount)

Önemli Kural: State asla doğrudan değiştirilemez (count = count + 1 YAPILAMAZ). Her zaman değiştirici fonksiyon kullanılmalıdır (setCount(count + 1)).

💻 Kod Örneği: Hayvan Beğenme Uygulaması (Etkinlik ve State Bir Arada)

Kullanıcı butona bastıkça listeye yeni bir hayvan ekleyen ve hayvanların üzerine tıklandıkça yanlarındaki kalp ikonunu büyüten bir sistem tasarlayalım.

import React, { useState } from 'react';

function HayvanGoster({ tur }) {
  const [kalpBoyutu, setKalpBoyutu] = useState(10); // Başlangıçta kalp 10 piksel

  const kalpArttir = () => {
    setKalpBoyutu(kalpBoyutu + 5); // Her tıklamada 5 piksel büyüt
  };

  return (
    <div onClick={kalpArttir} style={{ border: '1px solid #ccc', padding: '10px', cursor: 'pointer' }}>
      <span>Evcil Hayvan: {tur}</span>
      <span style={{ fontSize: `${kalpBoyutu}px`, color: 'red', marginLeft: '10px' }}>❤️</span>
    </div>
  );
}

function HayvanlarUygulamasi() {
  const [hayvan Listesi, setHayvanListesi] = useState(['Kedi', 'Köpek']);

  const rastgeleHayvanEkle = () => {
    const tumHayvanlar = ['Kedi', 'Köpek', 'Kuş', 'Tavşan', 'At'];
    const secilen = tumHayvanlar[Math.floor(Math.random() * tumHayvanlar.length)];
    
    // DİKKAT: state olan diziyi korumak için push yapmıyoruz, yeni bir dizi oluşturuyoruz (Immutable)
    setHayvanListesi([...hayvanListesi, secilen]);
  };

  return (
    <div>
      <button onClick={rastgeleHayvanEkle}>Yeni Hayvan Ekle</button>
      <div style={{ marginTop: '20px' }}>
        {hayvanListesi.map((hayvan, siraNo) => (
          <HayvanGoster key={siraNo} tur={hayvan} />
        ))}
      </div>
    </div>
  );
}

export default HayvanlarUygulamasi;

BÖLÜM 5: API Kullanımı ve Alt Bileşenden Üst Bileşene İletişim

Genellikle veriler alt bileşenlerde (örneğin bir arama çubuğunda) üretilir ama en tepedeki ana bileşene gönderilip orada internetten veri aratmak için kullanılır.

📐 Konu Anlatımı: Çocuktan Anneye (Child-to-Parent) İletişim ve Axios

React'te props'lar yukarıdan aşağıya gider demiştik. Peki alt bileşen üst bileşene nasıl haber uçurur?
Yöntem şu: Üst bileşen, alt bileşene bir fonksiyon hediye eder (prop olarak gönderir). Alt bileşen bu fonksiyonu tetiklediğinde, üst bileşendeki süreç başlamış olur.

İnternetten veri çekmek için ise popüler Axios kütüphanesini ve JavaScript'in asenkron (async/await) yapısını kullanırız.

💻 Kod Örneği: Resim Arama Uygulaması

Kullanıcının yazdığı kelimeyi alıp internetten (Unsplash benzeri bir sahte servisten) arama yapan bir akış tasarlayalım.

1. Arama Çubuğu Bileşeni (SearchBar.jsx) - Controlled Input Mantığı

import React, { useState } from 'react';

function SearchBar({ onAramaYap }) {
  const [arananKelime, setArananKelime] = useState('');

  const formGonderildi = (event) => {
    event.preventDefault(); // Sayfanın yenilenmesini engeller
    onAramaYap(arananKelime); // Üst bileşenden gelen fonksiyonu çalıştırıp kelimeyi yukarı yolluyoruz
  };

  return (
    <form onSubmit={formGonderildi}>
      <label>Ne aramak istersiniz?</label>
      <input 
        type="text" 
        value={arananKelime} 
        onChange={(e) => setArananKelime(e.target.value)} 
      />
      <button type="submit">Ara</button>
    </form>
  );
}

export default SearchBar;

2. Ana Uygulama Bileşeni (App.jsx)

import React, { useState } from 'react';
import SearchBar from './SearchBar';
import axios from 'axios';

function App() {
  const [resimler, setResimler] = useState([]);

  const resimleriInternettenGetir = async (kelime) => {
    // Temsili bir API adresi ve veri çekme işlemi
    const cevap = await axios.get('https://unsplash.com', {
      headers: { Authorization: 'Client-ID SIZIN_ANAHTARINIZ' },
      params: { query: kelime }
    });
    
    setResimler(cevap.data.results);
  };

  return (
    <div>
      <SearchBar onAramaYap={resimleriInternettenGetir} />
      <div>
        Bulunan Resim Sayısı: {resimler.length}
        {/* Resimler listelenirken her elemana benzersiz bir 'key' prop'u verilir */}
        {resimler.map((resim) => (
          <img key={resim.id} src={resim.urls.small} alt={resim.alt_description} />
        ))}
      </div>
    </div>
  );
}

export default App;

BÖLÜM 6: Formlar ve Karmaşık State Değişiklikleri (Dizi/Nesne Güncelleme Kuralları)

React geliştiricilerinin en çok hata yaptığı yer, state içindeki bir objeyi veya diziyi güncellerken eski veriyi yanlışlıkla silmek ya da doğrudan mutasyona uğratmaktır.

📐 Konu Anlatımı: Değiştirilemezlik (Immutability) Hileleri

Bir diziden eleman silerken, eklerken veya güncellerken orijinal diziyi bozmayan JavaScript fonksiyonlarını seçmeliyiz:

💻 Kod Örneği: Kitaplık Uygulaması (Ekleme, Silme, Güncelleme)

Tüm bu operasyonları tek bir temiz örnek üzerinde inceleyelim:

import React, { useState } from 'react';

function KitaplikUygulamasi() {
  const [kitaplar, setKitaplar] = useState([
    { id: 1, isim: 'Nutuk' },
    { id: 2, isim: 'Çalıkuşu' }
  ]);
  const [yeniKitapAdi, setYeniKitapAdi] = useState('');

  // 1. Kitap Ekleme (Array'e eleman ekleme)
  const kitapEkle = () => {
    const eklenecekKitap = {
      id: Math.round(Math.random() * 10000), // Sahte ID üretimi
      isim: yeniKitapAdi
    };
    setKitaplar([...kitaplar, eklenecekKitap]);
    setYeniKitapAdi('');
  };

  // 2. Kitap Silme (Array'den eleman silme)
  const kitapSil = (hedefId) => {
    const kalanKitaplar = kitaplar.filter((kitap) => {
      return kitap.id !== hedefId; // ID'si eşleşmeyenler kalsın, eşleşen silinsin
    });
    setKitaplar(kalanKitaplar);
  };

  // 3. Kitap Güncelleme (Array içindeki objeyi modifiye etme)
  const kitapIsmiGuncelle = (hedefId, yeniIsim) => {
    const guncelKitaplar = kitaplar.map((kitap) => {
      if (kitap.id === hedefId) {
        return { ...kitap, isim: yeniIsim }; // ID doğruysa ismini değiştir
      }
      return kitap; // Değilse aynen bırak
    });
    setKitaplar(guncelKitaplar);
  };

  return (
    <div style={{ padding: '20px' }}>
      <input 
        value={yeniKitapAdi} 
        onChange={(e) => setYeniKitapAdi(e.target.value)} 
        placeholder="Kitap adı yazın"
      />
      <button onClick={kitapEkle}>Ekle</button>

      <ul>
        {kitaplar.map((kitap) => (
          <li key={kitap.id} style={{ margin: '10px 0' }}>
            <span>{kitap.isim}</span>
            <button onClick={() => kitapSil(kitap.id)} style={{ marginLeft: '10px' }}>Sil</button>
            <button onClick={() => kitapIsmiGuncelle(kitap.id, kitap.isim + " (Güncellendi)")} style={{ marginLeft: '5px' }}>Güncelle</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default KitaplikUygulamasi;



Harika, hiç vakit kaybetmeden kaldığımız yerden, yani React'in en güçlü ve en çok karıştırılan mekanizmalarından biri olan useEffect ve Veri Kalıcılığı (Data Persistence) konularından devam ediyoruz.

Aynı disiplinle; dakikaları, süreleri ve "Önizleme" gibi gereksiz kelimeleri temizleyerek, konuları derinlemesine açıklamaya, örnekler ve uygulamalarla zenginleştirmeye devam ediyoruz.


BÖLÜM 7: Veri Kalıcılığı (JSON Server) ve useEffect Hook'unun Derinlikleri

Uygulamamızda tarayıcıyı yenileyince verilerin kaybolmasını istemiyorsak, bir veritabanı veya sahte bir API (JSON Server) kullanırız. Bu verileri sayfa ilk açıldığında otomatik olarak çekmek için ise useEffect mekanizmasına ihtiyaç duyarız.

📐 Konu Anlatımı: useEffect Nedir ve İnce Ayarları Nasıl Yapılır?

useEffect, React bileşenlerinin yaşam döngüsünde (Lifecycle) belirli anlarda yan etkileri (API'den veri çekmek, zamanlayıcı başlatmak, event dinleyicisi eklemek) çalıştırmak için kullanılır.

useEffect iki argüman alır: Birinci argüman çalışacak fonksiyon, ikinci argüman ise Bağımlılık Dizisidir (Dependency Array). Bu dizinin içeriğine göre useEffect üç farklı şekilde davranır:

  1. Dizi hiç verilmezse (useEffect(() => {})): Bileşen her render olduğunda (her state değişiminde) tekrar tekrar çalışır. (Dikkat: İçinde state değiştirirseniz sonsuz döngüye girer!)

  2. Boş dizi verilirse (useEffect(() => {}, [])): Bileşen ekrana ilk geldiğinde (Mount anında) sadece bir kez çalışır. API'den ilk verileri çekmek için en ideal yer burasıdır.

  3. Dizinin içine değişken konursa (useEffect(() => {}, [sayac])): Bileşen ilk açıldığında ve listenin içindeki sayac değişkeni her değiştiğinde çalışır.

Temizlik (Cleanup) Fonksiyonu:

useEffect içinden geriye bir fonksiyon döndürebilirsiniz. Bu fonksiyon, bileşen ekrandan kaybolurken (Unmount) veya efekt bir sonraki sefer çalışmadan hemen önce çalışır. Tarayıcı hafızasını temizlemek (örneğin arka plandaki click event dinleyicilerini silmek) için kullanılır.

💻 Kod Örneği: Veri Çeken ve Temizlik Yapan Akıllı Bileşen

Hem API'den veri çeken hem de arka planda kullanıcının ekranda nereye tıkladığını takip eden bir uygulama yapalım:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function KullaniciAyrinti({ kullaniciId }) {
  const [kullanici, setKullanici] = useState(null);

  // 1. Durum: kullaniciId değiştikçe internetten yeni verileri çek
  useEffect(() => {
    const veriGetir = async () => {
      try {
        // Temsili yerel JSON Server adresi
        const cevap = await axios.get(`http://localhost:3001/users/${kullaniciId}`);
        setKullanici(cevap.data);
      } catch (hata) {
        console.error("Veri çekilirken hata oluştu:", hata);
      }
    };

    veriGetir();
  }, [kullaniciId]); // kullaniciId değiştikçe bu fonksiyon tetiklenir

  // 2. Durum: Cleanup (Temizlik) mekanizmasının gösterimi
  useEffect(() => {
    const tiklamaTakip = (etkinlik) => {
      console.log("Tıklanan koordinat:", etkinlik.clientX, etkinlik.clientY);
    };

    // Ekrana tıklandığında çalışacak global dinleyici ekliyoruz
    document.body.addEventListener('click', tiklamaTakip);

    // Temizlik fonksiyonu: Bileşen ekrandan kalktığında dinleyiciyi kaldırır
    return () => {
      document.body.removeEventListener('click', tiklamaTakip);
      console.log("Dinleyici kaldırıldı, hafıza temizlendi.");
    };
  }, []); // Boş dizi: Sadece ilk açılışta dinleyici kurulur, kapanışta silinir

  if (!kullanici) return <div>Yükleniyor...</div>;

  return (
    <div style={{ border: '2px solid green', padding: '15px' }}>
      <h3>{kullanici.name}</h3>
      <p>E-posta: {kullanici.email}</p>
    </div>
  );
}

export default KullaniciAyrinti;

BÖLÜM 8: Context API Sistemi ile Merkezi Durum Yönetimi

Projeler büyüdükçe, en üstteki veriyi en alttaki bileşene göndermek için aradaki 5-6 bileşenden paslayarak geçirmek zorunda kalırız. Buna Prop-Drilling (Prop Delme) denir ve kodu okumayı çok zorlaştırır.

📐 Konu Anlatımı: Context API ve Custom Provider Tasarımı

Context API, uygulamamızın etrafına görünmez bir veri bulutu sarmamızı sağlar. Bu bulutun içine koyduğumuz state'lere ve fonksiyonlara, aradaki hiçbir bileşene dokunmadan, en alttaki bileşenden doğrudan useContext Hook'u ile erişebiliriz.

💻 Kod Örneği: Temalı Tema (Gündüz/Gece) ve Sepet Uygulaması

Gelin, tüm uygulamaya yön veren bir Context mimarisi kuralım.

1. Context Havuzunun Oluşturulması (TemaContext.jsx)

import React, { createContext, useState } from 'react';

// 1. Havuzu oluşturduk
export const TemaContext = createContext();

// 2. Özel bir Sağlayıcı (Custom Provider) bileşeni yazıyoruz
export function TemaProvider({ children }) {
  const [tema, setTema] = useState('gunduz'); // varsayılan tema gunduz

  const temaDegistir = () => {
    setTema(tema === 'gunduz' ? 'gece' : 'gunduz');
  };

  // Paylaşılacak verileri bir obje haline getiriyoruz
  const paylasilanlar = {
    tema,
    temaDegistir
  };

  return (
    <TemaContext.Provider value={paylasilanlar}>
      {children} 
    </TemaContext.Provider>
  );
}

2. Uygulamanın Sarmalanması (index.jsx veya App.jsx)

import React from 'react';
import { TemaProvider } from './TemaContext';
import SayfaIcerigi from './SayfaIcerigi';

function App() {
  return (
    // Uygulamayı veri bulutuyla sarmaladık
    <TemaProvider>
      <SayfaIcerigi />
    </TemaProvider>
  );
}

export default App;

3. Verinin En Alttaki Bileşenden Doğrudan Tüketilmesi (SayfaIcerigi.jsx)

import React, { useContext } from 'react';
import { TemaContext } from './TemaContext'; // Havuzu içe aktar

function SayfaIcerigi() {
  // Havuzdan ihtiyacımız olanları tek satırda çekiyoruz
  const { tema, temaDegistir } = useContext(TemaContext);

  const sayfaStili = {
    backgroundColor: tema === 'gunduz' ? '#ffffff' : '#333333',
    color: tema === 'gunduz' ? '#000000' : '#ffffff',
    height: '100vh',
    padding: '20px'
  };

  return (
    <div style={sayfaStili}>
      <h2>Context API Modülü</h2>
      <p>Şu anki uygulama teması: <strong>{tema.toUpperCase()}</strong></p>
      <button onClick={temaDegistir}>Temayı Değiştir</button>
    </div>
  );
}

export default SayfaIcerigi;

BÖLÜM 9: Gelişmiş Navigasyon, Portallar (React Portals) ve Modallar

Kendi yönlendirme (Routing) sistemimizi kurmayı, window.history.pushState fonksiyonunun mantığını ve DOM hiyerarşisini delerek tam ekran açılan Modalları (Açılır pencereleri) nasıl yapacağımızı inceleyeceğiz.

📐 Konu Anlatımı: React Portals ve useRef Mantığı

Normalde yazdığımız her bileşen, içinde bulunduğu Parent bileşenin HTML etiketlerinin altına eklenir. Ancak bir Modal (Açılır pencere) yapıyorsanız ve üst bileşende overflow: hidden veya garip bir CSS konumlandırması (position: relative) varsa, modal ekranın dışına taşabilir veya düzgün ortalanamaz.

React Portals (ReactDOM.createPortal), bir bileşeni kodsal olarak istediğimiz yerde yazıp, HTML ağacında tamamen farklı bir yere (örneğin doğrudan <body> etiketinin altındaki ayrı bir div içine) ışınlamamızı sağlar.

useRef ise React dünyasında HTML elementlerine doğrudan el uzatmamızı (referans almamızı) sağlayan akıllı bir kancadır.

💻 Kod Örneği: Portallar ile Tam Ekran Modal Yapımı

Önce public/index.html dosyamızda modal için ayrı bir yer açtığımızı varsayalım:

<div id="root"></div>
<!-- Modal buraya ışınlanacak -->
<div id="modal-container"></div>

Şimdi bu modal-container içine ışınlanacak Modal.jsx bileşenini kodlayalım:

import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';

function Modal({ kapatmaFonksiyonu, children, aksiyonlar }) {
  // Modal açıkken arka plandaki sayfanın kaydırılmasını engelleme (UX kuralı)
  useEffect(() => {
    document.body.style.overflow = 'hidden';
    
    // Bileşen ekrandan kalkınca eski haline getir (Cleanup)
    return () => {
      document.body.style.overflow = 'unset';
    };
  }, []);

  return ReactDOM.createPortal(
    // 1. Arka plan karartması (Gray overlay)
    <div 
      onClick={kapatmaFonksiyonu} 
      style={{
        position: 'fixed', inset: 0, backgroundColor: 'rgba(0,0,0,0.5)', 
        display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 999
      }}
    >
      {/* 2. Modal Kutusu (İçeriğe tıklanınca modalın kapanmasını engellemek için stopPropagation kullanıyoruz) */}
      <div 
        onClick={(e) => e.stopPropagation()} 
        style={{ backgroundColor: 'white', padding: '20px', borderRadius: '8px', minWidth: '300px' }}
      >
        <div className="modal-icerik">{children}</div>
        <div className="modal-butonlar" style={{ marginTop: '20px', display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
          {aksiyonlar}
        </div>
      </div>
    </div>,
    document.getElementById('modal-container') // Buraya ışınla!
  );
}

export default Modal;

Kullanımı (App.jsx):

import React, { useState } from 'react';
import Modal from './Modal';

function App() {
  const [modalAcik, setModalAcik] = useState(false);

  const modalButonlari = (
    <>
      <button onClick={() => setModalAcik(false)}>İptal</button>
      <button onClick={() => { alert("Silindi!"); setModalAcik(false); }} style={{ backgroundColor: 'red', color: 'white' }}>Onayla</button>
    </>
  );

  return (
    <div>
      <button onClick={() => setModalAcik(true)}>Hesabı Sil</button>

      {modalAcik && (
        <Modal kapatmaFonksiyonu={() => setModalAcik(false)} aksiyonlar={modalButonlari}>
          <h3>Dikkat!</h3>
          <p>Bu hesabı silmek istediğinize emin misiniz? Bu işlem geri alınamaz.</p>
        </Modal>
      )}
    </div>
  );
}

export default App;

BÖLÜM 10: Esnek, Yeniden Kullanılabilir ve Sıralanabilir Veri Tabloları (Sortable Table)

Kursun en yetenekli tasarım kalıplarından biri, içerisindeki verinin ne olduğunu bilmeyen, tamamen dışarıdan verilen bir konfigürasyon (config) dizisine göre kendi kendini çizen akıllı tablolardır.

📐 Konu Anlatımı: Dinamik Hücre Yönetimi ve Sıralama Algoritmaları

Bileşene verileri (data) düz bir dizi olarak veririz. Kolon başlıklarını ve o kolonun veriyi nasıl ekrana basacağını ise bir fonksiyon (render) yardımıyla config objesinde tanımlarız. JavaScript'in localeCompare fonksiyonu ile kelimeleri, matematiksel çıkarma ile de sayıları sıralarız.

💻 Kod Örneği: Dinamik ve Sıralanabilir Tablo Uygulaması

import React, { useState } from 'react';

function SortableTable({ data, config, keyFn }) {
  const [sortOrder, setSortOrder] = useState(null); // 'asc' (artan), 'desc' (azalan), null
  const [sortBy, setSortBy] = useState(null); // Hangi kolona göre sıralanıyor?

  const handleHeaderClick = (label, sortValueFn) => {
    // Sıralama fonksiyonu yoksa işlem yapma (O kolon sıralanamaz demektir)
    if (!sortValueFn) return;

    if (sortBy !== label) {
      setSortBy(label);
      setSortOrder('asc');
    } else if (sortOrder === 'asc') {
      setSortOrder('desc');
    } else {
      setSortBy(null);
      setSortOrder(null);
    }
  };

  // Sıralanmış veriyi hesapla
  let sortedData = data;
  if (sortOrder && sortBy) {
    const { sortValue } = config.find(col => col.label === sortBy);
    sortedData = [...data].sort((a, b) => {
      const valueA = sortValue(a);
      const valueB = sortValue(b);

      const reverseOrder = sortOrder === 'asc' ? 1 : -1;

      if (typeof valueA === 'string') {
        return valueA.localeCompare(valueB) * reverseOrder;
      } else {
        return (valueA - valueB) * reverseOrder;
      }
    });
  }

  return (
    <table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '20px' }}>
      <thead>
        <tr style={{ backgroundColor: '#f4f4f4', borderBottom: '2px solid #ccc' }}>
          {config.map((column) => (
            <th 
              key={column.label} 
              onClick={() => handleHeaderClick(column.label, column.sortValue)}
              style={{ padding: '10px', cursor: column.sortValue ? 'pointer' : 'default', textAlign: 'left' }}
            >
              {column.label} {sortBy === column.label ? (sortOrder === 'asc' ? '🔼' : '🔽') : ''}
            </th>
          ))}
        </tr>
      </thead>
      <tbody>
        {sortedData.map((rowData) => (
          <tr key={keyFn(rowData)} style={{ borderBottom: '1px solid #eee' }}>
            {config.map((column) => (
              <td key={column.label} style={{ padding: '10px' }}>
                {column.render(rowData)}
              </td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

// ---- UYGULAMA KULLANIMI ----
function MeyveUygulamasi() {
  const meyveler = [
    { id: 1, ad: 'Elma', renk: 'bg-red-500', skor: 5 },
    { id: 2, ad: 'Muz', renk: 'bg-yellow-500', skor: 3 },
    { id: 3, ad: 'Kivi', renk: 'bg-green-500', skor: 4 },
  ];

  const tabloYapilandirmasi = [
    { 
      label: 'Meyve Adı', 
      render: (meyve) => meyve.ad,
      sortValue: (meyve) => meyve.ad
    },
    { 
      label: 'Renk Kutusu', 
      render: (meyve) => <div style={{ width: '20px', height: '20px', borderRadius: '4px' }} className={meyve.renk}></div>
    },
    { 
      label: 'Lezzet Skoru', 
      render: (meyve) => <strong>{meyve.skor} / 5</strong>,
      sortValue: (meyve) => meyve.skor
    }
  ];

  return (
    <div style={{ padding: '20px' }}>
      <h2>Sıralanabilir Dinamik Tablo Bileşeni</h2>
      <SortableTable data={meyveler} config={tabloYapilandirmasi} keyFn={(m) => m.id} />
    </div>
  );
}

export default MeyveUygulamasi;

BÖLÜM 11: Özel Kancalar (Custom Hooks) Tasarlamak

React'te sadece arayüzü değil, state mantığını da paketleyip birden fazla bileşende tekrar kullanabiliriz.

📐 Konu Anlatımı: Custom Hook Yapma Mantığı

Eğer iki farklı bileşende tamamen aynı state tanımlamalarını, useEffect tetikleyicilerini ve form yönetim kurallarını yazıyorsanız, bu mantığı alıp ismi use ile başlayan bağımsız bir fonksiyona (useSayac, useForm, useSort) dönüştürebilirsiniz. Custom Hook'lar içlerinde normal React Hook'larını (useState, useEffect) barındırabilirler.

💻 Kod Örneği: useCounter (Sayaç Mantığını Kapsülleme) Hook Tasarımı

Önce kancamızı yazalım (useCounter.jsx):

import { useState, useEffect } from 'react';

function useCounter(baslangicDegeri = 0, artisMiktari = 1) {
  const [count, setCount] = useState(baslangicDegeri);

  useEffect(() => {
    console.log("Sayaç güncel değeri:", count);
  }, [count]);

  const arttir = () => setCount(count + artisMiktari);
  const azalt = () => setCount(count - artisMiktari);
  const sifirla = () => setCount(baslangicDegeri);

  // Dışarıya bileşenlerin kullanabileceği değişkenleri ve fonksiyonları dizi veya obje olarak fırlatıyoruz
  return { count, arttir, azalt, sifirla };
}

export default useCounter;

Şimdi bu kancayı yepyeni bir sayfada çocuk oyuncağı gibi kullanalım (SayacSayfasi.jsx):

import React from 'react';
import useCounter from './useCounter';

function SayacSayfasi() {
  // Tek satırda tüm sayaç lojiğini içeri aktardık!
  const { count, arttir, azalt, sifirla } = useCounter(10, 5); // 10'dan başla, 5'er 5'er art

  return (
    <div style={{ padding: '20px', textAlign: 'center' }}>
      <h2>Özel Kanca (Custom Hook) Pratiği</h2>
      <div style={{ fontSize: '32px', margin: '20px' }}>{count}</div>
      <button onClick={arttir} style={{ marginRight: '10px' }}>Beş Arttır</button>
      <button onClick={azalt} style={{ marginRight: '10px' }}>Beş Azalt</button>
      <button onClick={sifirla}>Sıfırla</button>
    </div>
  );
}

export default SayacSayfasi;



Harika, tempomuzu hiç düşürmeden tam gaz devam ediyoruz. Kursun en can alıcı, mülakatlarda en çok soru gelen ve büyük projelerin yönetim merkezini oluşturan useReducer, Redux Toolkit ve RTK Query dünyasına giriş yapıyoruz.

Süreleri, dakikaları ve "Önizleme" gibi işlem yapılmayacak kelimeleri yine temizleyerek; derinlemesine konu anlatımları, temiz kod örnekleri ve uygulamalarla devam ediyoruz.


BÖLÜM 12: useReducer Hook'u ile Durum Yönetimi (Redux'a Geçiş Köprüsü)

useState basit durumlar için harikadır. Ancak bir bileşende birden fazla birbiriyle ilişkili state varsa ve bu state'ler karmaşık kurallarla güncelleniyorsa, useReducer kullanmak işleri çok daha düzenli hale getirir.

📐 Konu Anlatımı: Reducer Fonksiyon Kuralları ve Action Nesneleri

useReducer mantığı, bir şirketteki iş akışına benzer:

Reducer Kuralları:

  1. State'i doğrudan mutasyona uğratamazsınız: state.count = 5 yazamazsınız. Her zaman yeni bir obje döndürmelisiniz.

  2. Saf (Pure) olmalıdır: İçinde rastgele sayılar (Math.random), tarih işlemleri (new Date()) veya API istekleri barındırmamalıdır. Sadece girdi alıp çıktı vermelidir.

💻 Kod Örneği: Gelişmiş Form ve Sayaç Yönetimi

Bir sayıyı hem arttırıp azaltabildiğimiz hem de input alanına yazılan sayı kadar toplu ekleme yapabildiğimiz bir sistem kuralım.

import React, { useReducer } from 'react';

// 1. Eylem Tiplerini Sabit (Constant) olarak tanımlıyoruz (Yazım hatalarını önlemek için)
const ETKINLIK = {
  ARTTIR: 'sayac/arttir',
  AZALT: 'sayac/azalt',
  GIRISI_AYARLA: 'form/giris_ayarla',
  TOPLU_EKLE: 'sayac/toplu_ekle'
};

// 2. Başlangıç Durumu (Initial State)
const baslangicDurumu = {
  sayac: 0,
  girdidekiSayi: 0
};

// 3. Reducer Fonksiyonu (Switch-Case yapısı topluluk kuralıdır)
function reducer(state, action) {
  switch (action.type) {
    case ETKINLIK.ARTTIR:
      return { ...state, sayac: state.sayac + 1 };
    case ETKINLIK.AZALT:
      return { ...state, sayac: state.sayac - 1 };
    case ETKINLIK.GIRISI_AYARLA:
      return { ...state, girdidekiSayi: action.payload };
    case ETKINLIK.TOPLU_EKLE:
      return { 
        ...state, 
        sayac: state.sayac + state.girdidekiSayi,
        girdidekiSayi: 0 // İşlem bitince input alanını sıfırla
      };
    default:
      return state; // Bilinmeyen bir eylem gelirse durumu aynen koru
  }
}

function ReducerSayfasi() {
  // useReducer bize güncel state'i ve eylemleri tetikleyecek dispatch fonksiyonunu verir
  const [state, dispatch] = useReducer(reducer, baslangicDurumu);

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: 'auto' }}>
      <h2>useReducer Kontrol Paneli</h2>
      <div style={{ fontSize: '24px', marginBottom: '15px' }}>Mevcut Sayı: {state.sayac}</div>
      
      <button onClick={() => dispatch({ type: ETKINLIK.ARTTIR })}>+1 Arttır</button>
      <button onClick={() => dispatch({ type: ETKINLIK.AZALT })} style={{ marginLeft: '10px' }}>-1 Azalt</button>
      
      <div style={{ marginTop: '20px' }}>
        <input 
          type="number" 
          value={state.girdidekiSayi || ''} 
          onChange={(e) => dispatch({ 
            type: ETKINLIK.GIRISI_AYARLA, 
            payload: parseInt(e.target.value) || 0 
          })}
          placeholder="Miktar yazın"
        />
        <button onClick={() => dispatch({ type: ETKINLIK.TOPLU_EKLE })} style={{ marginLeft: '10px' }}>
          Yazılan Miktarı Ekle
        </button>
      </div>
    </div>
  );
}

export default ReducerSayfasi;

BÖLÜM 13: Redux Toolkit (RTK) ile Merkezi Depo (Store) Mimarisi

useReducer harikadır ama sadece o bileşenin içinde yaşar. Uygulamanın en ucundaki bir bileşenden en diğer ucundaki bileşene kadar ortak bir veri deposu yönetmek istiyorsak, endüstri standardı olan Redux Toolkit kütüphanesini kullanırız.

📐 Konu Anlatımı: Slices, Store ve React-Redux Bağlantısı

Eski Redux yazmak çok fazla kod kalabalığı (boilerplate) gerektiriyordu. Redux Toolkit (RTK) ise bize Slice (Dilim) yapısını sundu. Bir Slice; o konuya ait başlangıç durumunu, reducer'ları ve action üreticilerini tek bir çatı altında otomatik olarak oluşturur.

Ayrıca RTK, arka planda Immer kütüphanesini kullandığı için reducer fonksiyonlarının içinde sanki state'i doğrudan değiştiriyormuş gibi rahatça kod yazmamıza (state.liste.push()) izin verir. Immer bunu güvenli bir kopyaya dönüştürür.

💻 Kod Örneği: Şarkı ve Film Oynatma Listesi Uygulaması

Gelin, iki farklı dilimi (Şarkılar ve Filmler) yöneten merkezi bir Redux deposu kuralım.

1. Şarkı Diliminin Oluşturulması (slices/songSlice.jsx)

import { createSlice } from '@reduxjs/toolkit';

const songSlice = createSlice({
  name: 'song',
  initialState: [],
  reducers: {
    // Immer sayesinde doğrudan push yapabiliyoruz!
    addSong(state, action) {
      state.push(action.payload);
    },
    removeSong(state, action) {
      // payload olarak gelen şarkının sırasını (index) bulup diziden siliyoruz
      const index = state.indexOf(action.payload);
      if (index !== -1) {
        state.splice(index, 1);
      }
    }
  }
});

// Action creator'ları otomatik üretilir, dışa aktarıyoruz
export const { addSong, removeSong } = songSlice.actions;
// Store'a eklemek için ana reducer'ı dışa aktarıyoruz
export default songSlice.reducer;

2. Merkezi Deponun Kurulması (store/index.jsx)

import { configureStore } from '@reduxjs/toolkit';
import songReducer from '../slices/songSlice';

export const store = configureStore({
  reducer: {
    // Depodaki 'songs' anahtarını songReducer yönetecek
    songs: songReducer
  }
});

3. Deponun Uygulamaya Bağlanması (index.jsx)

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { store } from './store';
import { Provider } from 'react-redux'; // Sağlayıcı kütüphane

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  // Depoyu tüm uygulamaya dağıtıyoruz
  <Provider store={store}>
    <App />
  </Provider>
);

4. Veri Çekme ve Eylem Tetikleme (App.jsx)

import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addSong, removeSong } from './slices/songSlice';

function App() {
  const dispatch = useDispatch();
  
  // Depodaki 'songs' dizisini anlık olarak dinliyoruz
  const sarkilar = useSelector((state) => {
    return state.songs;
  });

  const yeniSarkiEklemeSüreci = () => {
    const sahteSarkilar = ["Aramam", "Boşvermişim Dünyaya", "Dön Bak Dünyaya", "Ele Güne Karşı"];
    const rastgeleSarki = sahteSarkilar[Math.floor(Math.random() * sahteSarkilar.length)];
    
    // Depoya şarkı ekleme emrini gönderiyoruz
    dispatch(addSong(rastgeleSarki));
  };

  return (
    <div style={{ padding: '20px' }}>
      <h3>Müzik Listem</h3>
      <button onClick={yeniSarkiEklemeSüreci}>Listeye Rastgele Şarkı Ekle</button>
      
      <ul>
        {sarkilar.map((sarki, sira) => (
          <li key={sira} style={{ marginTop: '10px' }}>
            {sarki}
            <button 
              onClick={() => dispatch(removeSong(sarki))} 
              style={{ marginLeft: '15px', color: 'red' }}
            >
              Kaldır
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

BÖLÜM 14: RTK Query (Redux Toolkit Query) ile Modern Veri Çekme ve Önbellekleme

Büyük uygulamalarda asenkron API isteklerini (Thunks) manuel yönetmek, yükleniyor (loading) ve hata (error) durumlarını tek tek state'lere bağlamak çok yorucudur. RTK Query, veri çekme (data fetching) işini tamamen otomatize eden muazzam bir alt kütüphanedir.

📐 Konu Anlatımı: Önbellekleme (Caching), İstek Çiftleme Önleme ve Etiket (Tag) Sistemi

RTK Query, verileri otomatik olarak hafızada (cache) tutar. Örneğin A sayfasında kullanıcı listesini çektiniz, B sayfasına gidip tekrar A sayfasına dönerseniz internete bir daha istek atmaz; hafızadaki veriyi anında ekrana basar. Buna Request De-Duplication denir.

Peki sunucuda bir veri güncellendiğinde veya yeni bir eleman eklendiğinde ekrandaki eski önbellek verisi nasıl yenilenir? İşte burada Etiket Sistemi (Tag Invalidation) devreye girer:

💻 Kod Örneği: Albüm Yönetim API Sistemi

RTK Query ile hem veri çeken hem de veri ekleyen akıllı bir API mekanizması kuralım.

1. API Servisinin Tanımlanması (store/albumsApi.jsx)

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const albumsApi = createApi({
  reducerPath: 'albums', // Store'daki anahtar ismi
  baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3001' }), // Temel URL
  tagTypes: ['Album'], // Kullanılacak etiket tanımları
  
  endpoints: (builder) => ({
    // 1. Veri Çekme Sorgusu (Query)
    fetchAlbums: builder.query({
      providesTags: ['Album'], // Bu sorgu 'Album' etiketiyle işaretlendi
      query: (user) => {
        return {
          url: '/albums',
          params: { userId: user.id },
          method: 'GET'
        };
      }
    }),
    
    // 2. Veri Değiştirme/Ekleme İşlemi (Mutation)
    addAlbum: builder.mutation({
      invalidatesTags: ['Album'], // Bu işlem yapıldığında 'Album' etiketli tüm önbellekleri sil ve yenile!
      query: (user) => {
        return {
          url: '/albums',
          method: 'POST',
          body: {
            userId: user.id,
            title: 'Yeni Harika Albüm ' + Math.floor(Math.random() * 100)
          }
        };
      }
    })
  })
});

// RTK Query bizim için otomatik olarak kancalar üretti! İsimlendirme kuralı sabittir.
export const { useFetchAlbumsQuery, useAddAlbumMutation } = albumsApi;

2. Bileşen İçinde Sihirli Kancaların Kullanımı (AlbumsList.jsx)

import React from 'react';
import { useFetchAlbumsQuery, useAddAlbumMutation } from './store/albumsApi';

function AlbumsList({ user }) {
  // Sorgu kancası bize yüklenme durumunu, hatayı ve gelen veriyi otomatik nesne içinde verir
  const { data, error, isFetching } = useFetchAlbumsQuery(user);
  
  // Mutasyon kancası ise tetikleyeceğimiz bir fonksiyon ve işlem durumunu döner
  const [addAlbum, mutationResults] = useAddAlbumMutation();

  const albümOlusturmaSüreci = () => {
    addAlbum(user); // API'ye POST isteği gönderir ve etiket sistemini tetikler
  };

  if (isFetching) return <div>Albümler yükleniyor, lütfen bekleyin...</div>;
  if (error) return <div>Veriler getirilirken bir hata oluştu!</div>;

  return (
    <div style={{ padding: '15px', border: '1px dashed purple', marginTop: '10px' }}>
      <h4>{user.name} İsimli Kullanıcının Albümleri</h4>
      <button 
        disabled={mutationResults.isLoading} 
        onClick={albümOlusturmaSüreci}
      >
        {mutationResults.isLoading ? 'Ekleniyor...' : 'Yeni Albüm Ekle'}
      </button>

      <ul style={{ marginTop: '15px' }}>
        {data && data.map((album) => (
          <li key={album.id} style={{ padding: '5px 0' }}>
            📁 {album.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default AlbumsList;



Harika, enerjimizi ve tempomuzu koruyarak müfredatın son ve en modern zirve noktalarına giriş yapıyoruz: TypeScript ile Tip Güvenliği, React Router v6+ Mekanizmaları (Loaders) ve Legacy (Geçmiş) Versiyon Karşılaştırmaları.

Yine dakikaları, süreleri ve "Önizleme" gibi işlem yapılmayacak kelimeleri tamamen temizleyerek; derinlemesine konu anlatımları, temiz kod örnekleri ve mülakat kalitesinde uygulamalarla rehberimizi tamamlıyoruz.


BÖLÜM 15: TypeScript ile Güvenli React Mimarı Tasarlamak

JavaScript çok esnektir ama bir bileşene yanlış veri gönderdiğinizde (örneğin sayı bekleyen bir yere metin yolladığınızda) uygulama çalışma anında (runtime) çökebilir. TypeScript, bu hataları henüz kodu yazarken (derleme anında) yakalamamızı sağlar.

📐 Konu Anlatımı: Tip Tanımlamaları, Arayüzler (Interfaces) ve Generics

TypeScript dünyasında her değişkenin, fonksiyon argümanının ve bileşen props'unun tipini önceden ilan ederiz:

💻 Kod Örneği: TypeScript ve Props Entegrasyonu

Props alan temiz bir TypeScript bileşeni (UrunKarti.tsx) tasarlayalım:

import React from 'react';

// 1. Props'ların uymak zorunda olduğu kuralları (Kontratı) yazıyoruz
interface UrunKartiProps {
  id: number;
  baslik: string;
  fiyat: number;
  indirim Orani?: number; // Opsiyonel (Zorunlu değil)
  onSecimYap: (urunId: number) => void; // Fonksiyon tipi tanımı
}

// 2. React.FC (Functional Component) kullanarak tipleri bileşenimize giydiriyoruz
const UrunKarti: React.FC<UrunKartiProps> = ({ id, baslik, fiyat, indirimOrani, onSecimYap }) => {
  return (
    <div style={{ border: '1px solid #ddd', padding: '15px', borderRadius: '6px' }}>
      <h4>{baslik}</h4>
      <p>Fiyat: {fiyat} TL</p>
      
      {/* Tip Daraltma / Varlık Kontrolü */}
      {indirimOrani && <span style={{ color: 'green' }}>İndirim: %{indirimOrani}</span>}
      
      <button onClick={() => onSecimYap(id)} style={{ display: 'block', marginTop: '10px' }}>
        Ürünü Seç
      </button>
    </div>
  );
};

export default UrunKarti;

🛠️ Uygulama: TypeScript ile Esnek Bir Fonksiyon (Generics) Yazmak

Farklı türdeki dizilerin (sayı, kelime veya obje) ilk elemanını güvenli bir şekilde dönen genel bir fonksiyon tasarlayalım:

// 'T' harfi dışarıdan gelecek rastgele bir veri tipini temsil eder (Generic)
function ilkElemaniGetir<T>(dizi: T[]): T {
  return dizi[0];
}

// Kullanım Örnekleri:
const sayilar =;
const ilkSayi = ilkElemaniGetir<number>(sayilar); // Sonuç: 10 (Tipi kesinlikle number)

const isimler = ["Ahmet", "Mehmet", "Can"];
const ilkIsim = ilkElemaniGetir<string>(isimler); // Sonuç: "Ahmet" (Tipi kesinlikle string)

BÖLÜM 16: React Router v6+ ile Modern Sayfa Yönetimi ve Veri Yükleyiciler (Loaders)

Geleneksel React Router kullanımında önce sayfa açılır, ardından sayfa içindeki useEffect tetiklenir ve API'den veri beklenirdi. Bu durum ekranda sürekli "Yükleniyor..." yazısı görmemize neden olurdu. Modern React Router v6 ile birlikte gelen Loader yapısı bu akışı tamamen değiştirdi.

📐 Konu Anlatımı: Loader ve Programmatic Navigation Mantığı

Modern yaklaşımda, sayfa daha ekrana çizilmeden (render olmadan) önce yönlendirici (Router) arka planda verileri çeker (loader fonksiyonu ile). Veri hazır olduğunda sayfa anında tam dolu olarak ekrana gelir. Kullanıcı deneyimini (UX) muazzam derece artıran bir yöntemdir.

💻 Kod Örneği: Verileri Önceden Çeken (Loader) Detay Sayfası Tasarımı

Bir kütüphane projesindeki paket detaylarını sayfa açılmadan önce çeken akıllı bir yapı kuralım.

1. Rota Yapılandırması ve Veri Çekme Motoru (router.tsx)

import { createBrowserRouter } from 'react-router-dom';
import PaketDetaySayfasi from './PaketDetaySayfasi';
import axios from 'axios';

export const router = createBrowserRouter([
  {
    path: '/paketler/:paketAdi', // Dinamik rota
    element: <PaketDetaySayfasi />,
    
    // SİHİRLİ BÖLÜM: Sayfa açılmadan hemen önce bu fonksiyon otomatik çalışır
    loader: async ({ params }) => {
      // URL'deki :paketAdi parametresini alıyoruz
      const { paketAdi } = params;
      
      // Veriyi sayfa render edilmeden önce çekiyoruz
      const cevap = await axios.get(`https://npmjs.org{paketAdi}`);
      
      // Döndürdüğümüz bu veri doğrudan bileşenin içine akacak
      return cevap.data;
    }
  }
]);

2. Veriyi Tüketen Detay Bileşeni (PaketDetaySayfasi.jsx)

import React from 'react';
import { useLoaderData, useParams } from 'react-router-dom';

function PaketDetaySayfasi() {
  // URL'deki parametreye ihtiyacımız olursa okuyabiliriz
  const { paketAdi } = useParams();
  
  // Loader fonksiyonunun çektiği hazır veri tek satırda elimize geliyor!
  // Artık burada useEffect veya 'isLoading' state yönetimine gerek yok. Sayfa anında dolu açılır!
  const paketVerisi = useLoaderData();

  return (
    <div style={{ padding: '20px', border: '1px solid blue' }}>
      <h2>Modül Ayrıntıları: {paketAdi}</h2>
      <p><strong>Açıklama:</strong> {paketVerisi.description}</p>
      <p><strong>Son Sürüm:</strong> {paketVerisi['dist-tags']?.latest}</p>
      <p><strong>Lisans:</strong> {paketVerisi.license}</p>
    </div>
  );
}

export default PaketDetaySayfasi;

BÖLÜM 17: Legacy (Geçmiş) Versiyonlar ve Modern Mimari Karşılaştırması

Müfredatın son bölümü, eski kod tabanına sahip şirketlerde (Legacy projeler) çalışabilmeniz için geçmişteki kalıplarla günümüzdeki modern yapıları karşılaştırmaktadır. Mülakatlarda eski yapılardan yeni yapılara refactor (kodu iyileştirme) soruları sıkça gelir.

📊 Karşılaştırma Tablosu: Eski React vs Modern React

Özellik / Yapı

Eski Sektör Standardı (Legacy)

Modern Sektör Standardı (RTK / Hooks)

Bileşen Türü

Class-Based Components (Sınıf tabanlı, this.state ve render() metotları ile)

Functional Components (Fonksiyon tabanlı, tamamen Hooks mimarisi)

Yaşam Döngüsü

componentDidMount, componentWillUnmount metotları

Tek bir güçlü useEffect hook'u ve onun geri dönüş fonksiyonu (cleanup)

Merkezi Depo

Plain Redux (connect(), mapStateToProps, devasa switch-case kodları)

Redux Toolkit (createSlice, useSelector, useDispatch ve gömülü Immer kütüphanesi)

Veri Çekme (Async)

Redux Thunk ile manuel asenkron action yazımı ve elle yükleniyor state takibi

RTK Query ile tamamen otomatik önbelleklemeli kancalar (useQuery)

Navigasyon

BrowserRouter içinde iç içe <Route> etiketleri

Veriyi önceden yükleyen createBrowserRouter ve Loaders mimarisi


🏁 Müfredat Tasarımı ve Kurs Özeti

Tüm bu devasa müfredatı parça parça ele alarak; ham, süre içeren ve karmaşık yazılardan tamamen arındırılmış temiz bir geliştirici kılavuzu çıkardık.

Bu eğitim yolculuğunda sırasıyla:

  1. Temel JSX kuralları ile arayüz tasarlamayı,

  2. Props ve State ile sayfaları canlandırmayı,

  3. Context API ile veri ağları örüp prop-drilling'i engellemeyi,

  4. useReducer ve Redux Toolkit ile kurumsal ölçekte merkezi devlet depoları (store) inşa etmeyi,

  5. RTK Query sayesinde asenkron veri ağlarını tek satırda yönetmeyi,

  6. TypeScript ve React Router Loader yapılarıyla ise uygulamayı kırılmaz, hatasız ve yüksek performanslı hale getirmeyi öğrendiniz.

============================================================================================================

Harika bir karar! Bu iki büyük uygulama ve mülakat simülasyonu, öğrendiğiniz tüm teorik altyapıyı (React, Redux Toolkit, TypeScript) gerçek birer projeye dönüştürerek portfolyonuza eklemenizi sağlayacak.

Adım adım, en temiz kod standartlarıyla (Clean Code) ve mülakat kalitesinde ilerliyoruz.


BÖLÜM 1: E-Ticaret Sepet Uygulaması (React + RTK + TS)

Bu uygulamada TypeScript tipleri, Redux Toolkit mimarisi ve sepet hesaplama lojikleri bir arada yer alıyor.

📐 Mimari ve Tip Tanımlamaları (types.ts)

export interface Urun {
  id: number;
  ad: string;
  fiyat: number;
  gorselUrl: string;
}

export interface SepetElemani {
  urun: Urun;
  adet: number;
}

export interface SepetState {
  elemanlar: SepetElemani[];
  toplamFiyat: number;
  toplamAdet: number;
}

🧠 Redux Toolkit Sepet Dilimi (sepetSlice.ts)

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { SepetState, Urun } from './types';

const initialState: SepetState = {
  elemanlar: [],
  toplamFiyat: 0,
  toplamAdet: 0,
};

// Toplam fiyat ve adet hesaplayan yardımcı fonksiyon (Clean Code)
const sepetiHesapla = (state: SepetState) => {
  state.toplamAdet = state.elemanlar.reduce((toplam, eleman) => toplam + eleman.adet, 0);
  state.toplamFiyat = state.elemanlar.reduce((toplam, eleman) => toplam + (eleman.urun.fiyat * eleman.adet), 0);
};

export const sepetSlice = createSlice({
  name: 'sepet',
  initialState,
  reducers: {
    sepeteEkle: (state, action: PayloadAction<Urun>) => {
      const mevcutEleman = state.elemanlar.find(item => item.urun.id === action.payload.id);
      
      if (mevcutEleman) {
        mevcutEleman.adet += 1;
      } else {
        state.elemanlar.push({ urun: action.payload, adet: 1 });
      }
      sepetiHesapla(state);
    },
    sepettenCikar: (state, action: PayloadAction<number>) => {
      state.elemanlar = state.elemanlar.filter(item => item.urun.id !== action.payload);
      sepetiHesapla(state);
    },
    adetGuncelle: (state, action: PayloadAction<{ urunId: number; yeniAdet: number }>) => {
      const { urunId, yeniAdet } = action.payload;
      const mevcutEleman = state.elemanlar.find(item => item.urun.id === urunId);
      
      if (mevcutEleman && yeniAdet > 0) {
        mevcutEleman.adet = yeniAdet;
      }
      sepetiHesapla(state);
    },
    sepetiTemizle: (state) => {
      state.elemanlar = [];
      state.toplamFiyat = 0;
      state.toplamAdet = 0;
    }
  }
});

export const { sepeteEkle, sepettenCikar, adetGuncelle, sepetiTemizle } = sepetSlice.actions;
export default sepetSlice.reducer;

💻 Arayüz Bileşenleri (SepetUygulamasi.tsx)

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from './store'; // Store tipiniz
import { sepeteEkle, sepettenCikar, adetGuncelle } from './sepetSlice';
import { Urun } from './types';

const SAHTE_URUNLER: Urun[] = [
  { id: 101, ad: "Kablosuz Kulaklık", fiyat: 1500, gorselUrl: "https://picsum.photos" },
  { id: 102, ad: "Mekanik Klavye", fiyat: 2500, gorselUrl: "https://picsum.photos" },
  { id: 103, ad: "Oyuncu Mouse", fiyat: 950, gorselUrl: "https://picsum.photos" }
];

export const SepetUygulamasi: React.FC = () => {
  const dispatch = useDispatch();
  const { elemanlar, toplamFiyat, toplamAdet } = useSelector((state: RootState) => state.sepet);

  return (
    <div style={{ padding: '20px', display: 'flex', gap: '40px', fontFamily: 'sans-serif' }}>
      {/* Ürün Listesi */}
      <div style={{ flex: 1 }}>
        <h2>🏪 Mağaza Ürünleri</h2>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
          {SAHTE_URUNLER.map(urun => (
            <div key={urun.id} style={{ border: '1px solid #ddd', padding: '15px', borderRadius: '8px' }}>
              <img src={urun.gorselUrl} alt={urun.ad} style={{ borderRadius: '4px' }} />
              <h4>{urun.ad}</h4>
              <p>{urun.fiyat} TL</p>
              <button onClick={() => dispatch(sepeteEkle(urun))}>Sepete Ekle</button>
            </div>
          ))}
        </div>
      </div>

      {/* Sepet Detayı */}
      <div style={{ width: '400px', borderLeft: '2px solid #eee', paddingLeft: '20px' }}>
        <h2>🛒 Alışveriş Sepetim ({toplamAdet})</h2>
        {elemanlar.length === 0 ? (
          <p>Sepetiniz şu anda boş.</p>
        ) : (
          <>
            {elemanlar.map(item => (
              <div key={item.urun.id} style={{ display: 'flex', gap: '10px', marginBottom: '15px', alignItems: 'center' }}>
                <div style={{ flex: 1 }}>
                  <strong>{item.urun.ad}</strong>
                  <div>{item.urun.fiyat} TL x {item.adet}</div>
                </div>
                <input 
                  type="number" 
                  value={item.adet} 
                  style={{ width: '50px' }}
                  onChange={(e) => dispatch(adetGuncelle({ urunId: item.urun.id, yeniAdet: parseInt(e.target.value) || 1 }))}
                />
                <button onClick={() => dispatch(sepettenCikar(item.urun.id))} style={{ color: 'red' }}>Sil</button>
              </div>
            ))}
            <hr />
            <h3>Toplam Ödenecek: {toplamFiyat} TL</h3>
          </>
        )}
      </div>
    </div>
  );
};

BÖLÜM 2: Görev Yönetim Uygulaması (React + RTK + TS)

Bu projede veri filtreleme, durum değiştirme ve TypeScript tip katılıklarını içeren bir Todo/Task sistemi inşa ediyoruz.

📐 Tip Tanımlamaları (taskTypes.ts)

export type GorevDurumu = 'Bekliyor' | 'Yapılıyor' | 'Tamamlandı';

export interface Gorev {
  id: string;
  baslik: string;
  durum: GorevDurumu;
}

export interface GorevState {
  liste: Gorev[];
  mevcutFiltre: GorevDurumu | 'Tümü';
}

🧠 Redux Toolkit Görev Dilimi (gorevSlice.ts)

import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Gorev, GorevState, GorevDurumu } from './taskTypes';

const initialState: GorevState = {
  liste: [
    { id: '1', baslik: 'React Router v6 entegrasyonu yap', durum: 'Tamamlandı' },
    { id: '2', baslik: 'TypeScript arayüzlerini tanımla', durum: 'Yapılıyor' }
  ],
  mevcutFiltre: 'Tümü',
};

export const gorevSlice = createSlice({
  name: 'gorev',
  initialState,
  reducers: {
    gorevEkle: (state, action: PayloadAction<string>) => {
      const yeniGorev: Gorev = {
        id: Date.now().toString(),
        baslik: action.payload,
        durum: 'Bekliyor',
      };
      state.liste.push(yeniGorev);
    },
    durumDegistir: (state, action: PayloadAction<{ id: string; yeniDurum: GorevDurumu }>) => {
      const gorev = state.liste.find(item => item.id === action.payload.id);
      if (gorev) {
        gorev.durum = action.payload.yeniDurum;
      }
    },
    gorevSil: (state, action: PayloadAction<string>) => {
      state.liste = state.liste.filter(item => item.id !== action.payload);
    },
    filtreAyarla: (state, action: PayloadAction<GorevDurumu | 'Tümü'>) => {
      state.mevcutFiltre = action.payload;
    }
  }
});

export const { gorevEkle, durumDegistir, gorevSil, filtreAyarla } = gorevSlice.actions;
export default gorevSlice.reducer;

💻 Arayüz Bileşenleri (GorevUygulamasi.tsx)

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from './store';
import { gorevEkle, durumDegistir, gorevSil, filtreAyarla } from './gorevSlice';
import { GorevDurumu } from './taskTypes';

export const GorevUygulamasi: React.FC = () => {
  const dispatch = useDispatch();
  const { liste, mevcutFiltre } = useSelector((state: RootState) => state.gorev);
  const [metin, setMetin] = useState('');

  const handleEkle = (e: React.FormEvent) => {
    e.preventDefault();
    if (!metin.trim()) return;
    dispatch(gorevEkle(metin));
    setMetin('');
  };

  // Filtreleme mantığı (Derived State)
  const filtrelenmisGorevler = liste.filter(gorev => {
    if (mevcutFiltre === 'Tümü') return true;
    return gorev.durum === mevcutFiltre;
  });

  return (
    <div style={{ padding: '20px', maxWidth: '600px', margin: 'auto', fontFamily: 'sans-serif' }}>
      <h2>📋 Görev Yönetim Paneli (Kanban)</h2>

      {/* Görev Ekleme Formu */}
      <form onSubmit={handleEkle} style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        <input 
          type="text" 
          value={metin} 
          onChange={(e) => setMetin(e.target.value)}
          placeholder="Yeni görev açıklaması yazın..." 
          style={{ flex: 1, padding: '8px' }}
        />
        <button type="submit" style={{ padding: '8px 15px' }}>Ekle</button>
      </form>

      {/* Filtre Butonları */}
      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        {(['Tümü', 'Bekliyor', 'Yapılıyor', 'Tamamlandı'] as const).map(filtre => (
          <button 
            key={filtre}
            onClick={() => dispatch(filtreAyarla(filtre))}
            style={{ 
              padding: '5px 10px', 
              backgroundColor: mevcutFiltre === filtre ? '#333' : '#eee',
              color: mevcutFiltre === filtre ? '#fff' : '#000',
              border: 'none', borderRadius: '4px', cursor: 'pointer'
            }}
          >
            {filtre}
          </button>
        ))}
      </div>

      {/* Görev Listesi */}
      <div>
        {filtrelenmisGorevler.map(gorev => (
          <div key={gorev.id} style={{ 
            display: 'flex', justifyContent: 'space-between', alignItems: 'center',
            padding: '10px', border: '1px solid #eee', marginBottom: '8px', borderRadius: '4px'
          }}>
            <span style={{ textDecoration: gorev.durum === 'Tamamlandı' ? 'line-through' : 'none' }}>
              {gorev.baslik}
            </span>
            
            <div style={{ display: 'flex', gap: '10px' }}>
              <select 
                value={gorev.durum} 
                onChange={(e) => dispatch(durumDegistir({ id: gorev.id, yeniDurum: e.target.value as GorevDurumu }))}
              >
                <option value="Bekliyor">Bekliyor</option>
                <option value="Yapılıyor">Yapılıyor</option>
                <option value="Tamamlandı">Tamamlandı</option>
              </select>
              <button onClick={() => dispatch(gorevSil(gorev.id))} style={{ color: 'red', border: 'none', background: 'none', cursor: 'pointer' }}>❌</button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

BÖLÜM 3: Mülakat Simülasyonu (React Teknik Soru-Cevap)

Gerçekçi bir teknik mülakat (Technical Interview) senaryosu. Kıdemli Yazılım Geliştirici (Interviewer) soruyor, Aday (Siz) yanıtlıyor.

❓ Soru 1: React'te "Prop Drilling" nedir ve Redux Toolkit / Context API bunu nasıl çözer?

❓ Soru 2: useEffect içindeki Bağımlılık Dizisi (Dependency Array) kuralları nelerdir? Boş bırakırsak veya hiç yazmazsak ne olur?

❓ Soru 3: Redux Toolkit içindeki createSlice fonksiyonunun avantajı nedir? Arka plandaki "Immer" kütüphanesi ne işe yarar?

❓ Soru 4: React'te "Controlled Input" (Kontrollü Girdi) ne anlama gelir? Doğrudan DOM'a erişmekten farkı nedir?


***************************************************************************************************

Harika bir tercih! Teknik mülakatların en heyecanlı ve en çok belirleyici olan aşamasına geldik: Live Coding (Canlı Kodlama Testi).

Mülakatçılar bu testlerde sadece kodun çalışıp çalışmadığına bakmazlar; aynı zamanda problemi çözerken nasıl düşündüğünüze (Problem Solving), TypeScript tip güvenliğine ne kadar dikkat ettiğinize ve performansı nasıl optimize ettiğinize (Debounce lojiği gibi) bakarlar.

Önce mülakatçının size canlı yayında vereceği o zorlu problemi çözeceğiz, ardından kurduğumuz iki projeyi (Sepet ve Görev Yönetimi) TailwindCSS (v4 standartlarında) modern bir arayüze kavuşturacağız.


BÖLÜM 1: "Live Coding" (Canlı Kodlama) Sınavı

Senaryo: Mülakatçı ekranını paylaştı ve size şu problemi verdi:

"Bize TypeScript kullanarak bir arama fonksiyonu ve bu fonksiyonu optimize edecek özel bir debounced kancası (Custom Hook) yazın. Kullanıcı klavyeden her harf girdiğinde API'ye veya filtreleme motoruna gereksiz yük binmesini engelleyin. Süre sınırınız 15 dakika."

📐 Adım 1: Debounce Mekanizmasının Mantığı (Mülakatçıya Anlatım)

"Kodlamaya başlamadan önce mantığı açıklamak isterim: Kullanıcı yazı yazarken, her harfe bastığında bir asenkron işlem tetiklemek performansı düşürür. Debounce lojiği sayesinde, kullanıcının yazı yazmayı bitirmesini bekleriz. Belirlediğimiz süre (örneğin 300 milisaniye) boyunca yeni bir harfe basılmazsa, nihai kelimeyi işleme alırız. Bunu React'te useEffect ve setTimeout temizlik (cleanup) mekanizmasıyla çözeceğim."

💻 Adım 2: Custom Debounce Hook Yazımı (useDebounce.ts)

import { useState, useEffect } from 'react';

// Dışarıdan her türlü veri tipini (Generic <T>) ve gecikme süresini alan kanca
export function useDebounce<T>(value: T, delay: number = 300): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    // Kullanıcı her yazı yazdığında yeni bir zamanlayıcı (timer) başlatıyoruz
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // CLEANUP (Temizlik): Kullanıcı süre dolmadan tekrar bir harfe basarsa,
    // bir önceki zamanlayıcıyı iptal ediyoruz. En kritik performans optimizasyonu burasıdır!
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]); // value her değiştiğinde bu efekt sıfırlanıp baştan başlar

  return debouncedValue;
}

🛠️ Adım 3: Canlı Test ve Array Filtreleme Motoru (AramaBileseni.tsx)

import React, { useState, useMemo } from 'react';
import { useDebounce } from './useDebounce';

interface Kullanici {
  id: number;
  isim: string;
  departman: string;
}

const SAHTE_KULLANICILAR: Kullanici[] = [
  { id: 1, isim: "Can Yılmaz", departman: "Yazılım" },
  { id: 2, isim: "Merve Demir", departman: "Tasarım" },
  { id: 3, isim: "Ahmet Kaya", departman: "Yazılım" },
  { id: 4, isim: "Zeynep Çelik", departman: "Pazarlama" }
];

export const AramaBileseni: React.FC = () => {
  const [aramaMetni, setAramaMetni] = useState<string>('');
  
  // Yazılan metni 400ms boyunca debounce süzgecinden geçiriyoruz
  const debouncedArama = useDebounce<string>(aramaMetni, 400);

  // Performans için filtreleme işlemini useMemo ile sarmalıyoruz
  const filtrelenmisKullanicilar = useMemo(() => {
    if (!debouncedArama.trim()) return SAHTE_KULLANICILAR;
    
    return SAHTE_KULLANICILAR.filter(kullanici => 
      kullanici.isim.toLowerCase().includes(debouncedArama.toLowerCase()) ||
      kullanici.departman.toLowerCase().includes(debouncedArama.toLowerCase())
    );
  }, [debouncedArama]); // Sadece debouncedArama süresi dolup değiştiğinde filtreleme yapar!

  return (
    <div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md space-y-4">
      <h2 className="text-xl font-bold text-gray-900">👥 Akıllı Personel Arama</h2>
      <input 
        type="text"
        value={aramaMetni}
        onChange={(e) => setAramaMetni(e.target.value)}
        placeholder="İsim veya departman arayın..."
        className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none"
      />
      <div className="text-sm text-gray-500">
        API/Filtre Tetikleyici Kelime: <span className="font-mono text-red-500">"{debouncedArama}"</span>
      </div>
      <ul className="divide-y divide-gray-200">
        {filtrelenmisKullanicilar.map(k => (
          <li key={k.id} className="py-2 flex justify-between">
            <span className="font-medium text-gray-800">{k.isim}</span>
            <span className="text-sm bg-gray-100 px-2 py-0.5 rounded text-gray-600">{k.departman}</span>
          </li>
        ))}
      </ul>
    </div>
  );
};

Mülakatçı Notu: "Harika! Hem useEffect temizlik mekanizmasını kullandınız hem de useMemo ile gereksiz array operasyonlarının önüne geçtiniz. Geçer not aldınız."


BÖLÜM 2: Projelerin TailwindCSS ile Modern Tasarıma Kavuşturulması

Şimdi, bir önceki adımda yazdığımız E-Ticaret Sepeti ve Görev Yönetimi uygulamalarımızın arayüzlerini tamamen modern, göze hitap eden ve responsive bir hale getirelim.

🎨 1. E-Ticaret Sepet Uygulaması (ModernSepet.tsx)

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from './store';
import { sepeteEkle, sepettenCikar, adetGuncelle } from './sepetSlice';
import { Urun } from './types';

const SAHTE_URUNLER: Urun[] = [
  { id: 101, ad: "Pro Kablosuz Kulaklık", fiyat: 1500, gorselUrl: "https://picsum.photos" },
  { id: 102, ad: "RGB Mekanik Klavye", fiyat: 2500, gorselUrl: "https://picsum.photos" },
  { id: 103, ad: "Ergonomik Oyuncu Mouse", fiyat: 950, gorselUrl: "https://picsum.photos" }
];

export const ModernSepet: React.FC = () => {
  const dispatch = useDispatch();
  const { elemanlar, toplamFiyat, toplamAdet } = useSelector((state: RootState) => state.sepet);

  return (
    <div className="min-h-screen bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
      <div className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-8">
        
        {/* Sol Taraf: Ürünler Kataloğu */}
        <div className="lg:col-span-2">
          <h2 className="text-3xl font-extrabold text-gray-900 mb-6 flex items-center gap-2">
            <span>🏪</span> Teknoloji Mağazası
          </h2>
          <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
            {SAHTE_URUNLER.map(urun => (
              <div key={urun.id} className="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-shadow duration-300">
                <img src={urun.gorselUrl} alt={urun.ad} className="w-full h-48 object-cover" />
                <div className="p-5">
                  <h4 className="text-lg font-bold text-gray-900 mb-1">{urun.ad}</h4>
                  <p className="text-xl font-semibold text-blue-600 mb-4">{urun.fiyat.toLocaleString('tr-TR')} TL</p>
                  <button 
                    onClick={() => dispatch(sepeteEkle(urun))}
                    className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2.5 px-4 rounded-xl transition-colors duration-200"
                  >
                    Sepete Ekle
                  </button>
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Sağ Taraf: Alışveriş Sepeti Kutusu */}
        <div className="bg-white rounded-2xl shadow-sm border border-gray-100 p-6 h-fit sticky top-6">
          <h2 className="text-2xl font-bold text-gray-900 mb-6 flex items-center justify-between">
            <span>🛒 Sepetim</span>
            <span className="bg-blue-100 text-blue-800 text-sm font-semibold px-3 py-1 rounded-full">{toplamAdet} Ürün</span>
          </h2>
          
          {elemanlar.length === 0 ? (
            <div className="text-center py-12 text-gray-400">
              <span className="text-5xl block mb-2">🛍️</span>
              Sepetiniz henüz boş.
            </div>
          ) : (
            <div className="space-y-4">
              <div className="max-h-96 overflow-y-auto pr-1 space-y-4">
                {elemanlar.map(item => (
                  <div key={item.urun.id} className="flex gap-4 items-center bg-gray-50 p-3 rounded-xl">
                    <div className="flex-1">
                      <h5 className="font-semibold text-gray-800 text-sm">{item.urun.ad}</h5>
                      <span className="text-xs text-gray-500">{item.urun.fiyat.toLocaleString('tr-TR')} TL</span>
                    </div>
                    <div className="flex items-center gap-2">
                      <input 
                        type="number" 
                        min="1"
                        value={item.adet} 
                        onChange={(e) => dispatch(adetGuncelle({ urunId: item.urun.id, yeniAdet: parseInt(e.target.value) || 1 }))}
                        className="w-14 text-center border border-gray-300 rounded-lg p-1 text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
                      />
                      <button 
                        onClick={() => dispatch(sepettenCikar(item.urun.id))}
                        className="text-red-500 hover:text-red-700 p-1 text-sm font-medium"
                      >
                        Sil
                      </button>
                    </div>
                  </div>
                ))}
              </div>
              
              <div className="border-t border-gray-100 pt-4 mt-6">
                <div className="flex justify-between text-gray-600 mb-2">
                  <span>Ara Toplam</span>
                  <span>{toplamFiyat.toLocaleString('tr-TR')} TL</span>
                </div>
                <div className="flex justify-between text-gray-900 font-bold text-xl mb-4">
                  <span>Genel Toplam</span>
                  <span className="text-blue-600">{toplamFiyat.toLocaleString('tr-TR')} TL</span>
                </div>
                <button className="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-xl shadow-sm transition-colors duration-200">
                  Alışverişi Tamamla
                </button>
              </div>
            </>
          )}
        </div>

      </div>
    </div>
  );
};

🎨 2. Görev Yönetim Uygulaması (ModernGorevUygulamasi.tsx)

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from './store';
import { gorevEkle, durumDegistir, gorevSil, filtreAyarla } from './gorevSlice';
import { GorevDurumu } from './taskTypes';

export const ModernGorevUygulamasi: React.FC = () => {
  const dispatch = useDispatch();
  const { liste, mevcutFiltre } = useSelector((state: RootState) => state.gorev);
  const [metin, setMetin] = useState<string>('');

  const handleEkle = (e: React.FormEvent) => {
    e.preventDefault();
    if (!metin.trim()) return;
    dispatch(gorevEkle(metin));
    setMetin('');
  };

  const filtrelenmisGorevler = liste.filter(gorev => {
    if (mevcutFiltre === 'Tümü') return true;
    return gorev.durum === mevcutFiltre;
  });

  // Durumlara göre şık rozet (Badge) renkleri atayan fonksiyon
  const durumRengi Getir = (durum: GorevDurumu) => {
    if (durum === 'Tamamlandı') return 'bg-green-50 text-green-700 border-green-200';
    if (durum === 'Yapılıyor') return 'bg-amber-50 text-amber-700 border-amber-200';
    return 'bg-blue-50 text-blue-700 border-blue-200';
  };

  return (
    <div className="min-h-screen bg-slate-50 py-12 px-4 sm:px-6">
      <div className="max-w-2xl mx-auto bg-white rounded-2xl shadow-sm border border-slate-100 p-6 sm:p-8">
        <h2 className="text-2xl font-bold text-slate-800 mb-6 flex items-center gap-2">
          <span>📋</span> Proje Görev Paneli
        </h2>

        {/* Form Alanı */}
        <form onSubmit={handleEkle} className="flex gap-2 mb-6">
          <input 
            type="text" 
            value={metin} 
            onChange={(e) => setMetin(e.target.value)}
            placeholder="Yapılacak yeni bir görev yazın..." 
            className="flex-1 px-4 py-2.5 border border-slate-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500 placeholder-slate-400 text-slate-700 text-sm"
          />
          <button 
            type="submit"
            className="bg-indigo-600 hover:bg-indigo-700 text-white font-medium px-5 rounded-xl text-sm transition-colors duration-200"
          >
            Ekle
          </button>
        </form>

        {/* Filtreleme Çubuğu */}
        <div className="flex flex-wrap gap-1.5 mb-6 bg-slate-50 p-1.5 rounded-xl border border-slate-100">
          {(['Tümü', 'Bekliyor', 'Yapılıyor', 'Tamamlandı'] as const).map(filtre => (
            <button 
              key={filtre}
              onClick={() => dispatch(filtreAyarla(filtre))}
              className={`flex-1 text-center py-2 text-xs font-semibold rounded-lg transition-all duration-200 ${
                mevcutFiltre === filtre 
                  ? 'bg-white text-indigo-600 shadow-sm border border-slate-100' 
                  : 'text-slate-500 hover:text-slate-800 hover:bg-slate-100'
              }`}
            >
              {filtre}
            </button>
          ))}
        </div>

        {/* Görev Listesi */}
        <div className="space-y-3">
          {filtrelenmisGorevler.length === 0 ? (
            <div className="text-center py-8 text-slate-400 text-sm">
              Bu filtreye uygun görev bulunamadı.
            </div>
          ) : (
            filtrelenmisGorevler.map(gorev => (
              <div 
                key={gorev.id} 
                className={`flex justify-between items-center p-4 border rounded-xl transition-all duration-200 ${
                  gorev.durum === 'Tamamlandı' ? 'border-slate-100 bg-slate-50/50' : 'border-slate-200 hover:border-slate-300'
                }`}
              >
                <span className={`text-sm font-medium ${
                  gorev.durum === 'Tamamlandı' ? 'line-through text-slate-400' : 'text-slate-700'
                }`}>
                  {gorev.baslik}
                </span>
                
                <div className="flex items-center gap-3">
                  <select 
                    value={gorev.durum} 
                    onChange={(e) => dispatch(durumDegistir({ id: gorev.id, yeniDurum: e.target.value as GorevDurumu }))}
                    className={`text-xs font-semibold px-2 py-1.5 border rounded-lg focus:outline-none ${durumRengiGetir(gorev.durum)}`}
                  >
                    <option value="Bekliyor">Bekliyor</option>
                    <option value="Yapılıyor">Yapılıyor</option>
                    <option value="Tamamlandı">Tamamlandı</option>
                  </select>
                  <button 
                    onClick={() => dispatch(gorevSil(gorev.id))}
                    className="text-slate-400 hover:text-red-500 p-1 text-sm transition-colors duration-150"
                    title="Görevi Sil"
                  >
                    ❌
                  </button>
                </div>
              </div>
            ))
          )}
        </div>

      </div>
    </div>
  );
};