Yazılarımız

OfisData

REACT FORM VALİDASYON YAPMAK

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

Form validasyon herhangi bir uygulamanın en sık yapılan işlerinden. Kayıt formu, iletişim formu, ödeme formu, profil güncelleme; her birinde "boş bırakmayın", "geçerli e-posta girin", "şifre en az 8 karakter" gibi kontroller. React'ta validasyon yapmanın onlarca yolu var; doğru yaklaşımı seçmek hem developer experience hem kullanıcı deneyimini belirler.

Modern React form validasyonu üç katman kullanır: state management (form state nerede tutulacak), validation logic (kurallar nasıl tanımlanacak) ve UI feedback (hatalar nasıl gösterilecek). Bu katmanların altında yatan controlled component ve event mantığı React öğrenme dokümantasyonunda temelden anlatılır; React Hook Form ve Zod kombinasyonu ise bu üç katmanı temiz şekilde ayıran güncel standart.

React'te form validasyonunun iki ucu var: her şeyi elle yöneten manuel yaklaşım ve şema tabanlı modern kütüphane hattı (React Hook Form + Zod gibi). İkisinin de yeri var — küçük formda kütüphane şişirme, büyük formda manuel kod bakım borcudur; strateji seçimi formun karmaşıklığına göre yapılır.

Yaklaşımlar

1. Manuel state ile

useState hooks kullanarak temel doğrulama:

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ı.

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

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>
  );
}

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

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.

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'
});

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>
  );
}

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.

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.

İ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

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

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.

 Vimaj