İçeriğe geç
react

React'te State Yönetimi: Temel Kavramlar ve Uygulamalar

Tarık Tunç
React'te State Yönetimi: Temel Kavramlar ve Uygulamalar

React'te State Yönetimi: Temel Kavramlar ve Uygulamalar

State (durum), React bileşenlerinin dinamik ve etkileşimli olmasını sağlayan temel kavramdır. Kullanıcı etkileşimleri, API yanıtları veya zamanlayıcılar gibi olaylara bağlı olarak değişen verileri temsil eder. Bu yazıda React'te state yönetiminin temellerini, state tasarım prensiplerini ve yaygın hataları detaylı olarak inceleyeceğiz.

State vs Props: Temel Fark

State ve props arasındaki farkı anlamak React'in en önemli kavramlarından biridir:

Özellik State Props
Sahiplik Bileşenin kendisi yönetir Üst bileşen tarafından aktarılır
Değiştirilebilirlik setState ile değiştirilebilir Salt okunur (read-only)
Amaç Bileşenin iç durumunu yönetmek Bileşeni yapılandırmak
Değiştiğinde Re-render tetiklenir Re-render tetiklenir
State, bir bileşenin "hafızası"dır. Render'lar arasında bilgiyi korur ve değiştiğinde bileşenin güncellenmesini sağlar.

useState ile State Tanımlama

Modern React'te state yönetiminin en temel aracı useState hook'udur:

import { useState } from 'react';

function RegistrationForm() {
  // Farklı türlerde state tanımlama
  const [name, setName] = useState('');           // string
  const [age, setAge] = useState(0);              // number
  const [isAgreed, setIsAgreed] = useState(false); // boolean
  const [hobbies, setHobbies] = useState([]);      // array
  const [address, setAddress] = useState({         // object
    city: '',
    district: '',
    zipCode: ''
  });

  const handleAddressChange = (field, value) => {
    // Object state güncelleme - spread operatörü ile
    setAddress(prev => ({
      ...prev,
      [field]: value
    }));
  };

  const addHobby = (hobby) => {
    // Array state güncelleme - spread operatörü ile
    setHobbies(prev => [...prev, hobby]);
  };

  const removeHobby = (index) => {
    setHobbies(prev => prev.filter((_, i) => i !== index));
  };

  return (
    <form>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Adınız"
      />
      <input
        type="number"
        value={age}
        onChange={(e) => setAge(Number(e.target.value))}
        placeholder="Yaşınız"
      />
      <input
        value={address.city}
        onChange={(e) => handleAddressChange('city', e.target.value)}
        placeholder="Şehir"
      />
      <label>
        <input
          type="checkbox"
          checked={isAgreed}
          onChange={(e) => setIsAgreed(e.target.checked)}
        />
        Koşulları kabul ediyorum
      </label>
    </form>
  );
}

State Güncelleme Kuralları

1. State Güncellemeleri Asenkrondur

React, performans için state güncellemelerini toplu (batch) olarak işler:

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

  const handleClick = () => {
    // YANLIŞ: Her ikisi de aynı count değerini kullanır
    setCount(count + 1); // count hala 0
    setCount(count + 1); // count hala 0 - sonuç: 1

    // DOĞRU: Fonksiyonel güncelleme ile önceki değeri kullan
    setCount(prev => prev + 1); // 0 + 1 = 1
    setCount(prev => prev + 1); // 1 + 1 = 2 - sonuç: 2
  };

  return (
    <div>
      <p>Sayaç: {count}</p>
      <button onClick={handleClick}>+2 Artır</button>
    </div>
  );
}

Uyarı: State güncelleme fonksiyonu çağrıldıktan hemen sonra state değeri hala eski değeri korur. Yeni değer bir sonraki render'da kullanılabilir. Bu nedenle güncellenmiş değere bağlı işlemlerde fonksiyonel güncelleme pattern'ini kullanın.

2. State'i Doğrudan Mutate Etmeyin

function TodoManager() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'React öğren', done: false }
  ]);

  // YANLIŞ - doğrudan mutasyon
  const wrongToggle = (id) => {
    const todo = todos.find(t => t.id === id);
    todo.done = !todo.done; // Doğrudan mutasyon!
    setTodos(todos); // Aynı referans - React değişikliği algılamaz
  };

  // DOĞRU - yeni referans oluşturma
  const correctToggle = (id) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  };

  // Ekleme
  const addTodo = (text) => {
    setTodos(prev => [...prev, { id: Date.now(), text, done: false }]);
  };

  // Silme
  const removeTodo = (id) => {
    setTodos(prev => prev.filter(todo => todo.id !== id));
  };

  // Güncelleme
  const updateTodo = (id, newText) => {
    setTodos(prev =>
      prev.map(todo =>
        todo.id === id ? { ...todo, text: newText } : todo
      )
    );
  };

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => correctToggle(todo.id)}>Tamamla</button>
          <button onClick={() => removeTodo(todo.id)}>Sil</button>
        </li>
      ))}
    </ul>
  );
}

State Tasarım Prensipleri

1. Derived State Kullanmayın

Mevcut state'ten hesaplanabilecek değerleri ayrı bir state olarak tanımlamayın:

function ShoppingCart() {
  const [items, setItems] = useState([]);

  // YANLIŞ: totalPrice ayrı bir state olarak tanımlamak
  // const [totalPrice, setTotalPrice] = useState(0);

  // DOĞRU: Mevcut state'ten hesapla (derived value)
  const totalPrice = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);
  const hasItems = items.length > 0;

  return (
    <div>
      <h3>Sepetim ({itemCount} ürün)</h3>
      {items.map(item => (
        <div key={item.id}>
          {item.name} - {item.price} TL x {item.quantity}
        </div>
      ))}
      {hasItems && <p><strong>Toplam: {totalPrice.toFixed(2)} TL</strong></p>}
    </div>
  );
}

İpucu: Bir değer mevcut state veya props'tan hesaplanabiliyorsa onu ayrı bir state olarak tanımlamayın. Bu, state senkronizasyon sorunlarını önler ve kodunuzu basitleştirir. Hesaplama maliyetli ise useMemo kullanabilirsiniz.

2. State'i Mümkün Olduğunca Düz Tutun

// KÖTÜ - iç içe yapı
const [formData, setFormData] = useState({
  user: {
    personal: {
      name: '',
      email: ''
    },
    preferences: {
      theme: 'light',
      language: 'tr'
    }
  }
});

// İYİ - düz yapı
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [theme, setTheme] = useState('light');
const [language, setLanguage] = useState('tr');

State Lifting (State'i Yukarı Taşıma)

İki kardeş bileşenin aynı state'i paylaşması gerektiğinde, state ortak üst bileşene taşınır:

function TemperatureConverter() {
  const [celsius, setCelsius] = useState('');

  const fahrenheit = celsius !== ''
    ? ((parseFloat(celsius) * 9/5) + 32).toFixed(1)
    : '';

  return (
    <div>
      <h3>Sıcaklık Dönüştürücü</h3>
      <TemperatureInput
        label="Celsius"
        value={celsius}
        onChange={setCelsius}
      />
      <TemperatureInput
        label="Fahrenheit"
        value={fahrenheit}
        onChange={(f) => {
          const c = ((parseFloat(f) - 32) * 5/9).toFixed(1);
          setCelsius(c);
        }}
      />
    </div>
  );
}

function TemperatureInput({ label, value, onChange }) {
  return (
    <label>
      {label}:
      <input
        type="number"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    </label>
  );
}

Bilgi: React 18 ile birlikte state güncellemeleri artık tüm durumlarda (event handler'lar, setTimeout, Promise'ler) otomatik olarak batch (toplu) olarak işlenir. Bu, gereksiz re-render'ları azaltarak performansı artırır.

Lazy Initialization

useState'e başlangıç değeri olarak maliyetli bir hesaplama gerekiyorsa, fonksiyon geçirin:

function ExpensiveComponent() {
  // YANLIŞ: Her render'da hesaplanır
  // const [data, setData] = useState(expensiveCalculation());

  // DOĞRU: Yalnızca ilk render'da hesaplanır
  const [data, setData] = useState(() => {
    const saved = localStorage.getItem('appData');
    return saved ? JSON.parse(saved) : { items: [], settings: {} };
  });

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

Sonuç

State yönetimi, React uygulamalarının kalbidir. Doğru state tasarımı, uygulamanızın performansını, bakım kolaylığını ve hata ayıklama sürecini doğrudan etkiler. State'i immutable tutmak, derived state'ten kaçınmak, state'i mümkün olduğunca düz yapıda tutmak ve fonksiyonel güncelleme pattern'ini kullanmak en temel en iyi uygulamalardır. Daha karmaşık state senaryoları için useReducer hook'unu ve Context API'yi incelemenizi öneriyorum.

Kaynaklar

İlgili Yazılar