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.