İçeriğe geç
react

React'te Form Yönetimi: Controlled ve Uncontrolled Component'ler

Tarık Tunç
React'te Form Yönetimi: Controlled ve Uncontrolled Component'ler

React'te Form Yönetimi: Controlled ve Uncontrolled Component'ler

Form yönetimi, web uygulamalarının en karmaşık ve kritik parçalarından biridir. Kullanıcı kayıt formları, arama kutuları, ödeme sayfaları ve ayar panelleri gibi sayısız senaryoda form işlemleriyle karşılaşırız. React'te formlar iki temel yaklaşımla yönetilir: Controlled ve Uncontrolled component'ler. Bu rehberde her iki yaklaşımı, validasyon tekniklerini ve gerçek dünya uygulamalarını detaylı olarak inceleyeceğiz.

Controlled Component'ler

Controlled component'lerde form elemanlarının değeri React state'i tarafından kontrol edilir. Her değişiklik bir event handler aracılığıyla state'i günceller ve React, güncel state'i form elemanına yansıtır:

import { useState } from 'react';

function RegistrationForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    password: '',
    confirmPassword: '',
    role: 'user',
    agreeToTerms: false,
    interests: []
  });

  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;

    if (type === 'checkbox' && name === 'interests') {
      setFormData(prev => ({
        ...prev,
        interests: checked
          ? [...prev.interests, value]
          : prev.interests.filter(i => i !== value)
      }));
    } else {
      setFormData(prev => ({
        ...prev,
        [name]: type === 'checkbox' ? checked : value
      }));
    }
  };

  const validate = () => {
    const newErrors = {};
    if (!formData.name.trim()) newErrors.name = 'Ad zorunludur';
    if (!formData.email.includes('@')) newErrors.email = 'Geçerli e-posta giriniz';
    if (formData.password.length < 8) newErrors.password = 'En az 8 karakter olmalıdır';
    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = 'Şifreler eşleşmiyor';
    }
    if (!formData.agreeToTerms) newErrors.agreeToTerms = 'Koşulları kabul etmelisiniz';

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      console.log('Form gönderiliyor:', formData);
    }
  };

  return (
    <form onSubmit={handleSubmit} noValidate>
      <div className="form-group">
        <label htmlFor="name">Ad Soyad</label>
        <input
          id="name"
          name="name"
          value={formData.name}
          onChange={handleChange}
          className={errors.name ? 'input-error' : ''}
        />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>

      <div className="form-group">
        <label htmlFor="email">E-posta</label>
        <input
          id="email"
          name="email"
          type="email"
          value={formData.email}
          onChange={handleChange}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>

      <div className="form-group">
        <label>Rol</label>
        <select name="role" value={formData.role} onChange={handleChange}>
          <option value="user">Kullanıcı</option>
          <option value="editor">Editör</option>
          <option value="admin">Yönetici</option>
        </select>
      </div>

      <div className="form-group">
        <label>
          <input
            type="checkbox"
            name="agreeToTerms"
            checked={formData.agreeToTerms}
            onChange={handleChange}
          />
          Koşulları kabul ediyorum
        </label>
        {errors.agreeToTerms && <span className="error">{errors.agreeToTerms}</span>}
      </div>

      <button type="submit">Kayıt Ol</button>
    </form>
  );
}

Uncontrolled Component'ler

Uncontrolled component'lerde form değerleri DOM tarafından yönetilir. useRef ile değerlere erişilir:

import { useRef } from 'react';

function QuickSearchForm({ onSearch }) {
  const inputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    onSearch(inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={inputRef} defaultValue="" placeholder="Hızlı arama..." />
      <button type="submit">Ara</button>
    </form>
  );
}

// Dosya yükleme - her zaman uncontrolled
function FileUpload({ onUpload }) {
  const fileInputRef = useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    const files = fileInputRef.current.files;
    if (files.length > 0) {
      onUpload(files);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="file" ref={fileInputRef} accept="image/*" multiple />
      <button type="submit">Yükle</button>
    </form>
  );
}

Bilgi: Dosya input'ları (<input type="file">) her zaman uncontrolled'dır çünkü değerleri güvenlik nedeniyle programatik olarak ayarlanamaz. Bunun dışında React ekibi çoğu durumda controlled component'leri önermektedir.

Controlled vs Uncontrolled Karşılaştırma

Özellik Controlled Uncontrolled
Değer kaynağı React state DOM
Anlık validasyon Kolay Zor
Koşullu alan devre dışı bırakma Kolay Zor
Performans (çok alan) Her tuşta re-render Re-render yok
Karmaşıklık Daha fazla kod Daha az kod

React Hook Form ile Modern Form Yönetimi

import { useForm } from 'react-hook-form';

function ContactForm() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
    reset
  } = useForm({
    defaultValues: {
      name: '',
      email: '',
      subject: '',
      message: ''
    }
  });

  const onSubmit = async (data) => {
    await fetch('/api/contact', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    reset();
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register('name', { required: 'Ad zorunludur' })}
        placeholder="Adınız"
      />
      {errors.name && <span className="error">{errors.name.message}</span>}

      <input
        {...register('email', {
          required: 'E-posta zorunludur',
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: 'Geçerli e-posta giriniz'
          }
        })}
        placeholder="E-posta"
      />
      {errors.email && <span className="error">{errors.email.message}</span>}

      <textarea
        {...register('message', {
          required: 'Mesaj zorunludur',
          minLength: { value: 10, message: 'En az 10 karakter' }
        })}
        placeholder="Mesajınız"
        rows={5}
      />
      {errors.message && <span className="error">{errors.message.message}</span>}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Gönderiliyor...' : 'Gönder'}
      </button>
    </form>
  );
}

İpucu: React Hook Form, uncontrolled component yaklaşımını kullanarak performansı optimize eder. Büyük formlar, dinamik alanlar ve karmaşık validasyon kuralları olan projelerde native controlled component yaklaşımı yerine React Hook Form kullanmayı değerlendirin.

Dinamik Form Alanları

function DynamicForm() {
  const [fields, setFields] = useState([{ id: 1, value: '' }]);

  const addField = () => {
    setFields(prev => [...prev, { id: Date.now(), value: '' }]);
  };

  const removeField = (id) => {
    setFields(prev => prev.filter(field => field.id !== id));
  };

  const updateField = (id, value) => {
    setFields(prev =>
      prev.map(field => field.id === id ? { ...field, value } : field)
    );
  };

  return (
    <div>
      {fields.map((field, index) => (
        <div key={field.id} className="dynamic-field">
          <input
            value={field.value}
            onChange={(e) => updateField(field.id, e.target.value)}
            placeholder={`Alan ${index + 1}`}
          />
          {fields.length > 1 && (
            <button onClick={() => removeField(field.id)}>Kaldır</button>
          )}
        </div>
      ))}
      <button onClick={addField}>Alan Ekle</button>
    </div>
  );
}

Uyarı: Controlled ve uncontrolled yaklaşımları aynı input'ta karıştırmayın. Bir input value prop'u alıyorsa controlled'dır ve mutlaka bir onChange handler'ı da olmalıdır. defaultValue kullanıyorsanız uncontrolled'dır. Bu iki yaklaşımı aynı input'ta kullanmak React uyarılarına neden olur.

Sonuç

React'te form yönetimi, controlled ve uncontrolled yaklaşımların avantajlarını anlamakla başlar. Çoğu durumda controlled component'ler daha güvenli ve yönetilebilir bir yaklaşım sunar. Büyük ve karmaşık formlarda React Hook Form gibi kütüphaneler geliştirme sürecini hızlandırır ve performansı artırır. Doğru validasyon stratejisi ve erişilebilir form tasarımı her zaman önceliğiniz olmalıdır.

Kaynaklar

İlgili Yazılar