Ders İçeriği
Kontrollü Bileşenler (Controlled Components)
React'te form yönetimi yaparken en önemli kavramlardan biri kontrollü bileşenlerdir. Kontrollü bileşenler, form elemanlarının değerlerinin React state'i tarafından kontrol edildiği bileşenlerdir. Bu yaklaşımda, form elemanının değeri her zaman React state'inden gelir ve değişiklikler state güncelleme fonksiyonları aracılığıyla yapılır.
Kontrollü bileşenlerin temel prensibi, "single source of truth" (tek doğruluk kaynağı) kavramına dayanır. Form elemanının değeri sadece React state'inde tutulur ve DOM'daki değer bu state'ten türetilir. Bu sayede, form verilerinin durumu her zaman öngörülebilir ve kontrol edilebilir olur.
Basit bir kontrollü input örneği şu şekildedir:
import React, { useState } from 'react';
function ControlledInput() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
return (
<div>
<input
type="text"
value={value}
onChange={handleChange}
placeholder="Adınızı girin"
/>
<p>Girilen değer: {value}</p>
</div>
);
}
Bu örnekte, input elemanının değeri value
state'inden gelir ve kullanıcı her karakter yazdığında onChange
event handler'ı çalışarak state'i günceller. Bu sayede, input'un değeri her zaman React state'i ile senkronize kalır.
Kontrollü bileşenlerin avantajları oldukça fazladır. İlk olarak, form verilerine her zaman erişiminiz vardır ve bu verileri istediğiniz zaman kullanabilirsiniz. İkinci olarak, form validasyonunu gerçek zamanlı olarak yapabilirsiniz. Üçüncü olarak, form verilerini başka bileşenlerle paylaşmak çok kolaydır. Son olarak, form verilerini test etmek ve debug yapmak daha kolaydır.
Daha karmaşık bir kontrollü form örneği:
function UserRegistrationForm() {
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
agreeToTerms: false
});
const handleInputChange = (event) => {
const { name, value, type, checked } = event.target;
setFormData(prevData => ({
...prevData,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('Form verileri:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Ad:
<input
type="text"
name="firstName"
value={formData.firstName}
onChange={handleInputChange}
required
/>
</label>
</div>
<div>
<label>
Soyad:
<input
type="text"
name="lastName"
value={formData.lastName}
onChange={handleInputChange}
required
/>
</label>
</div>
<div>
<label>
E-posta:
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
required
/>
</label>
</div>
<div>
<label>
Şifre:
<input
type="password"
name="password"
value={formData.password}
onChange={handleInputChange}
required
/>
</label>
</div>
<div>
<label>
Şifre Tekrar:
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleInputChange}
required
/>
</label>
</div>
<div>
<label>
<input
type="checkbox"
name="agreeToTerms"
checked={formData.agreeToTerms}
onChange={handleInputChange}
required
/>
Kullanım şartlarını kabul ediyorum
</label>
</div>
<button type="submit">Kayıt Ol</button>
</form>
);
}
Bu örnekte, tüm form verileri tek bir state objesi içinde tutulur ve handleInputChange
fonksiyonu tüm input elemanları için kullanılır. Bu yaklaşım, kod tekrarını azaltır ve form yönetimini daha verimli hale getirir.
Kontrolsüz Bileşenler (Uncontrolled Components)
Kontrolsüz bileşenler, form elemanlarının değerlerinin DOM tarafından yönetildiği bileşenlerdir. Bu yaklaşımda, React state'i kullanmak yerine, DOM'un kendi internal state'ini kullanırız ve form verilerine ihtiyaç duyduğumuzda DOM'dan okuruz.
Kontrolsüz bileşenlerde, form elemanlarına erişmek için useRef
hook'u kullanılır:
import React, { useRef } from 'react';
function UncontrolledForm() {
const nameRef = useRef();
const emailRef = useRef();
const handleSubmit = (event) => {
event.preventDefault();
const formData = {
name: nameRef.current.value,
email: emailRef.current.value
};
console.log('Form verileri:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Ad:
<input
type="text"
ref={nameRef}
defaultValue="Varsayılan Ad"
/>
</label>
</div>
<div>
<label>
E-posta:
<input
type="email"
ref={emailRef}
/>
</label>
</div>
<button type="submit">Gönder</button>
</form>
);
}
Kontrolsüz bileşenlerde value
prop'u yerine defaultValue
prop'u kullanılır. Bu, elemanın başlangıç değerini belirler ancak sonrasında React bu değeri kontrol etmez.
Kontrolsüz bileşenlerin avantajları şunlardır: daha az kod yazımı gerektirir, performans açısından daha verimli olabilir (özellikle büyük formlarda), üçüncü parti kütüphanelerle entegrasyon daha kolaydır. Ancak dezavantajları da vardır: gerçek zamanlı validasyon yapmak zordur, form verilerine sürekli erişim yoktur, test etmek daha zordur.
Form Verilerini Yakalama
Form verilerini yakalamak için çeşitli yöntemler kullanılabilir. En yaygın yöntemler kontrollü bileşenler, kontrolsüz bileşenler ve FormData API'sidir.
FormData API Kullanımı
Modern tarayıcılarda desteklenen FormData API'si, form verilerini kolayca yakalamak için kullanılabilir:
function FormDataExample() {
const handleSubmit = (event) => {
event.preventDefault();
const formData = new FormData(event.target);
// Form verilerini obje olarak al
const data = Object.fromEntries(formData.entries());
console.log('Form verileri:', data);
// Dosya yükleme için
const file = formData.get('avatar');
if (file) {
console.log('Yüklenen dosya:', file.name);
}
};
return (
<form onSubmit={handleSubmit}>
<input name="firstName" placeholder="Ad" required />
<input name="lastName" placeholder="Soyad" required />
<input name="email" type="email" placeholder="E-posta" required />
<input name="avatar" type="file" accept="image/*" />
<select name="country">
<option value="tr">Türkiye</option>
<option value="us">Amerika</option>
<option value="de">Almanya</option>
</select>
<textarea name="message" placeholder="Mesajınız"></textarea>
<button type="submit">Gönder</button>
</form>
);
}
Özel Form Hook'u
Form yönetimini daha verimli hale getirmek için özel bir hook oluşturabilirsiniz:
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (event) => {
const { name, value, type, checked } = event.target;
const newValue = type === 'checkbox' ? checked : value;
setValues(prev => ({
...prev,
[name]: newValue
}));
// Gerçek zamanlı validasyon
if (validate && touched[name]) {
const fieldErrors = validate({ ...values, [name]: newValue });
setErrors(prev => ({
...prev,
[name]: fieldErrors[name]
}));
}
};
const handleBlur = (event) => {
const { name } = event.target;
setTouched(prev => ({
...prev,
[name]: true
}));
// Blur olayında validasyon
if (validate) {
const fieldErrors = validate(values);
setErrors(prev => ({
...prev,
[name]: fieldErrors[name]
}));
}
};
const handleSubmit = async (onSubmit) => {
setIsSubmitting(true);
// Tüm alanları touched olarak işaretle
const allTouched = Object.keys(values).reduce((acc, key) => {
acc[key] = true;
return acc;
}, {});
setTouched(allTouched);
// Validasyon kontrolü
if (validate) {
const formErrors = validate(values);
setErrors(formErrors);
const hasErrors = Object.keys(formErrors).some(key => formErrors[key]);
if (hasErrors) {
setIsSubmitting(false);
return;
}
}
try {
await onSubmit(values);
} catch (error) {
console.error('Form gönderimi başarısız:', error);
} finally {
setIsSubmitting(false);
}
};
const reset = () => {
setValues(initialValues);
setErrors({});
setTouched({});
setIsSubmitting(false);
};
return {
values,
errors,
touched,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
reset
};
}
// Kullanımı
function ContactForm() {
const initialValues = {
name: '',
email: '',
message: ''
};
const validate = (values) => {
const errors = {};
if (!values.name) {
errors.name = 'İsim gerekli';
} else if (values.name.length < 2) {
errors.name = 'İsim en az 2 karakter olmalı';
}
if (!values.email) {
errors.email = 'E-posta gerekli';
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
errors.email = 'Geçersiz e-posta formatı';
}
if (!values.message) {
errors.message = 'Mesaj gerekli';
} else if (values.message.length < 10) {
errors.message = 'Mesaj en az 10 karakter olmalı';
}
return errors;
};
const {
values,
errors,
touched,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
reset
} = useForm(initialValues, validate);
const onSubmit = async (formData) => {
// API çağrısı simülasyonu
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Form gönderildi:', formData);
alert('Mesajınız başarıyla gönderildi!');
reset();
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleSubmit(onSubmit);
}}>
<div>
<label>
İsim:
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
/>
</label>
{touched.name && errors.name && (
<span style={{ color: 'red' }}>{errors.name}</span>
)}
</div>
<div>
<label>
E-posta:
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
/>
</label>
{touched.email && errors.email && (
<span style={{ color: 'red' }}>{errors.email}</span>
)}
</div>
<div>
<label>
Mesaj:
<textarea
name="message"
value={values.message}
onChange={handleChange}
onBlur={handleBlur}
rows="4"
/>
</label>
{touched.message && errors.message && (
<span style={{ color: 'red' }}>{errors.message}</span>
)}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Gönderiliyor...' : 'Gönder'}
</button>
<button type="button" onClick={reset}>
Temizle
</button>
</form>
);
}