REACT FORM VALİDASYON YAPMAK

Vimaj10 dk okuma
React mavi atom logosu ile form input mockup ve yeşil tik kırmızı çarpı validasyon sembollerinin minimal hero kompozisyonu

React form validasyon; controlled component, React Hook Form ve Zod gibi araçlarla state management, validation logic ve UI feedback katmanlarını ayırarak kullanıcı dostu bir doğrulama akışı kurmayı sağlar. Bu yazıda manuel state'ten şema tabanlı modern yaklaşıma kadar seçenekleri kod örnekleriyle öğreneceksiniz.

1. Yaklaşımlar

1. Manuel state ile

useState hooks kullanarak temel doğrulama; controlled component ve event mantığı React öğrenme dokümantasyonunda temelden anlatılır:

import { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [errors, setErrors] = useState({});

  const validate = () => {
    const newErrors = {};
    if (!email.includes('@')) {
      newErrors.email = 'Geçerli e-posta girin';
    }
    if (password.length < 8) {
      newErrors.password = 'Şifre en az 8 karakter';
    }
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    if (validate()) {
      // submit logic
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      {errors.email && <p>{errors.email}</p>}

      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      {errors.password && <p>{errors.password}</p>}

      <button type="submit">Giriş</button>
    </form>
  );
}

Basit formlar için yeterli; ama 5+ alanlı formlarda kod karmaşıklaşır.

2. React Hook Form (modern standart)

npm install react-hook-form

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

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        type="email"
        {...register('email', {
          required: 'E-posta gerekli',
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: 'Geçersiz e-posta'
          }
        })}
      />
      {errors.email && <p>{errors.email.message}</p>}

      <input
        type="password"
        {...register('password', {
          required: 'Şifre gerekli',
          minLength: { value: 8, message: 'En az 8 karakter' }
        })}
      />
      {errors.password && <p>{errors.password.message}</p>}

      <button type="submit">Giriş</button>
    </form>
  );
}

useState yok; daha temiz, daha az re-render, daha hızlı.

2. React Hook Form avantajları

  • Performance: Her tuş basışında re-render olmaz; sadece submit veya error değişiminde
  • Uncontrolled: ref tabanlı; daha az state yükü
  • Built-in validation: required, min, max, pattern, validate
  • Schema integration: Yup, Zod, Joi ile entegre
  • TypeScript desteği: Mükemmel type inference

3. Zod ile schema validation

Yapılandırılmış validation için Zod en modern kütüphane:

npm install zod @hookform/resolvers
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

const schema = z.object({
  email: z.string().email('Geçersiz e-posta'),
  password: z.string().min(8, 'En az 8 karakter'),
  age: z.number().min(18, '18 yaşından büyük olmalı'),
  acceptTerms: z.boolean().refine(val => val === true, {
    message: 'Şartları kabul etmelisiniz'
  })
});

type FormData = z.infer<typeof schema>;

function RegisterForm() {
  const { register, handleSubmit, formState: { errors } } =
    useForm<FormData>({
      resolver: zodResolver(schema)
    });

  return (
    <form onSubmit={handleSubmit(data => console.log(data))}>
      <input {...register('email')} />
      {errors.email && <p>{errors.email.message}</p>}

      <input type="password" {...register('password')} />
      {errors.password && <p>{errors.password.message}</p>}

      <input
        type="number"
        {...register('age', { valueAsNumber: true })}
      />
      {errors.age && <p>{errors.age.message}</p>}

      <input type="checkbox" {...register('acceptTerms')} />
      {errors.acceptTerms && <p>{errors.acceptTerms.message}</p>}

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

4. Zod schema örnekleri

Conditional validation

const schema = z.object({
  type: z.enum(['individual', 'company']),
  taxId: z.string().optional()
}).refine((data) => {
  if (data.type === 'company' && !data.taxId) {
    return false;
  }
  return true;
}, {
  message: 'Şirket için vergi numarası zorunlu',
  path: ['taxId']
});

Custom validation

const schema = z.object({
  password: z.string()
    .min(8, 'En az 8 karakter')
    .regex(/[A-Z]/, 'En az bir büyük harf')
    .regex(/[0-9]/, 'En az bir rakam')
    .regex(/[^A-Za-z0-9]/, 'En az bir özel karakter'),
  confirmPassword: z.string()
}).refine(data => data.password === data.confirmPassword, {
  message: 'Şifreler eşleşmiyor',
  path: ['confirmPassword']
});

Async validation

const checkEmail = async (email: string) => {
  const res = await fetch(`/api/check-email?email=${email}`);
  const data = await res.json();
  return data.available;
};

const schema = z.object({
  email: z.string().email().refine(
    async (email) => await checkEmail(email),
    'Bu e-posta zaten kayıtlı'
  )
});
VS Code dark theme React TSX editöründe Zod schema tanımı ve useForm zodResolver entegrasyonu kodunun syntax highlight ile sunumu

5. UI feedback patterns

Inline error

Her alanın altında o alanın hatası. En yaygın yaklaşım.

<div>
  <input {...register('email')} className={errors.email ? 'border-red' : ''} />
  {errors.email && (
    <p className="text-red text-sm">{errors.email.message}</p>
  )}
</div>

Summary error

Formun üstünde tüm hataları liste halinde göster.

{Object.keys(errors).length > 0 && (
  <div className="error-summary">
    <h3>Hatalar:</h3>
    <ul>
      {Object.entries(errors).map(([key, error]) => (
        <li key={key}>{error.message}</li>
      ))}
    </ul>
  </div>
)}

Toast notification

Submit sonrası genel hata için. Detay inline olarak gösterilir.

6. Validation timing

ModeDavranış
onSubmit (varsayılan)Sadece submit'te validate
onBlurAlan focus kaybedince validate
onChangeHer tuşa basışta validate
onTouchedİlk dokunuş + sonraki tüm değişiklikler
allHem onBlur hem onChange

UX için onBlur veya onTouched tipik tercih. onChange çok agresif; kullanıcı yazarken hata mesajı çıkar (sinir bozucu).

const { register, formState } = useForm({
  mode: 'onBlur'
});

7. Async submit ile loading state

function ContactForm() {
  const { register, handleSubmit, formState: { isSubmitting } } = useForm();

  const onSubmit = async (data) => {
    try {
      await fetch('/api/contact', {
        method: 'POST',
        body: JSON.stringify(data)
      });
      alert('Mesaj gönderildi');
    } catch (error) {
      alert('Hata oluştu');
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      <textarea {...register('message')} />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Gönderiliyor...' : 'Gönder'}
      </button>
    </form>
  );
}

8. Erişilebilirlik

Validation UX için erişilebilirlik önemli:

<label htmlFor="email">E-posta</label>
<input
  id="email"
  type="email"
  aria-invalid={errors.email ? 'true' : 'false'}
  aria-describedby={errors.email ? 'email-error' : undefined}
  {...register('email')}
/>
{errors.email && (
  <p id="email-error" role="alert">
    {errors.email.message}
  </p>
)}

aria-invalid ve aria-describedby ekran okuyucu kullanıcıları için kritik.

9. Server-side validation

Client-side validation kullanıcı deneyimi için; ama gerçek güvenlik için server'da da validasyon zorunlu. Saldırgan client'i atlayabilir.

Schema paylaşımı

İki yerde de validation yapıldığında: client hızlı feedback verir, server gerçek koruma sağlar. Zod schema'sı paylaşılabilir; aynı schema hem React'ta hem Node.js'te çalışır.

Tarayıcıda render edilmiş kayıt formunda inline hata mesajları kırmızı border odak ve gönderiliyor loading state butonun farklı durumlarda kompozisyonu

10. Dikkat Edilecek Tuzaklar

  1. Tüm validasyonu client'ta yapmak. Server'da da validation şart
  2. onChange mode kullanmak. Çok agresif; kullanıcıyı sinirlendirir
  3. Hata mesajları belirsiz. "Hata" yerine "E-posta @ içermeli"
  4. Aria attribute'ları eklememek. Erişilebilirlik kaybı
  5. isSubmitting kontrol etmemek. Çoklu submit; çift kayıt
  6. Schema'yı her yerde tekrar yazmak. Zod schema paylaşılabilir

11. Disiplinin Yerleşmesi

React form yönetimi modern frontend'in temel becerilerinden. Disiplini pekiştirerek ilerlemek için React eğitimi programları form yönetimi ve state konularını birlikte aktarır.

Toparlarsak

React form validasyon; React Hook Form + Zod modern standart. useState bazlı manuel yaklaşım küçük formlar için yeterli; orta-büyük formlar için React Hook Form performance ve okunabilirlik avantajı sağlar. Zod schema validation type-safe ve okunabilir. Validation timing UX için kritik; onBlur veya onTouched tipik tercih. Erişilebilirlik için aria attribute'ları gerekli. Client-side validation UX için; server-side güvenlik için zorunlu.

Sıkça Sorulan Sorular

React Hook Form mu Formik mı?

2024+ standardı <strong>React Hook Form</strong>. Formik 2018-2021 popülerdi ama daha çok re-render yapar, bundle büyük, performance düşük. React Hook Form uncontrolled component yaklaşımıyla daha hızlı, daha küçük bundle, TypeScript desteği daha iyi. Yeni projeler için React Hook Form; mevcut Formik projesi olduğu gibi devam edebilir.

Zod yerine Yup kullanabilir miyim?

Evet, Yup hâlâ popüler. Yup daha basit syntax; Zod daha güçlü TypeScript inference. Yup'ta type belirtmek için manual gerek; Zod schema'sından otomatik type çıkarır. Yeni projeler Zod'a kayıyor; Yup'ı sürdüren projeler de var. Hangisi seçilirse seçilsin React Hook Form ile resolver paketi var.

Validation onChange'de mi onBlur'da mı olmalı?

UX için onBlur veya onTouched ideal. onChange çok agresif; kullanıcı 'a' yazınca 'geçersiz e-posta' uyarısı kötü deneyim. onBlur kullanıcı alanı bitirip başka yere geçince validate eder; doğal. onTouched daha akıllı: ilk dokunuştan sonra her değişiklikte kontrol; yazarken hatayı görüp düzeltme imkanı.

Server-side validation gerçekten gerekli mi?

Mutlaka. Client-side validation sadece UX için; saldırgan tarayıcı tool'larıyla atlayabilir. POSTMAN veya curl ile doğrudan API'ye istek atılabilir; client validation devre dışı. Server'da aynı kuralları tekrar uygulayın. Zod schema her iki tarafta paylaşılabilir; tek schema iki yerde çalışır.

Form state'i Redux'a koymak gerekli mi?

Hayır, neredeyse her zaman gereksiz. Form state lokal; sayfa içinde tutulur. Redux/Zustand'a koyma sebebi: form sayfalar arası persist etmesi gerekiyorsa (multi-step form) veya başka componentler form değerlerine erişiyorsa. Çoğu form için React Hook Form'un kendi state'i yeterli.

Multi-step form (sihirbaz) nasıl yapılır?

İki yaklaşım. Birincisi: tek useForm, adımları conditional render; advance/back butonlarıyla aktif step değişir. Form state korunur. İkincisi: her step kendi formu, ama global state'te (Zustand) ortak veri tutulur. İkinci yaklaşım daha esnek ama karmaşık. Çoğu uygulama için tek useForm + step state yeterli.

Submit sonrası başarı/başarısızlık nasıl gösterilir?

isSubmitting ile loading state; isSubmitSuccessful ile başarı; submitCount ile total attempt. Genel pattern: try/catch içinde fetch; başarılı ise toast notification + form reset; hata ise hata mesajı. Modal veya inline başarı mesajı kullanıcıya net feedback verir. Form yeniden submit edilmemeli; kullanıcı yönlendirilir veya form sıfırlanır.

 Vimaj