React Hook Form Kompletan Vodič: Validacija, TypeScript i Više

React Hook Form Kompletan Vodič: Validacija, TypeScript i VišeReact Hook Form Kompletan Vodič: Validacija, TypeScript i Više

28. pro. 2025. - 16 min

Roko Ponjarac

Roko Ponjarac

Software Engineer


Izrada formi u Reactu oduvijek je bila zahtjevna. Rukovanje stanjem forme, validacijom i greškama čini vašu bazu koda kompliciranijom. S fokusom na performanse, React Hook Form rješava ove probleme smanjenjem ponovnih renderiranja i pojednostavljenjem upravljanja formama.

Ovaj vodič vam govori sve što trebate znati o React Hook Formu. Naučit ćete kako instalirati, koristiti i prilagoditi wrapper komponente, kao i kako ih koristiti s Material-UI.

Zašto Biste Trebali Koristiti React Hook Form

Kada koristite React za rukovanje formama na stari način, morate pratiti stanje svakog input polja. Ovaj način rada uzrokuje probleme s performansama i preveliki kod. React Hook Form radi stvari drugačije koristeći nekontrolirane komponente i refove.

Biblioteka podržava HTML standardnu validaciju. Ima odličnu podršku za TypeScript. Čini bundle manjim jer nema ovisnosti. Ove stvari čine React Hook Form najboljim izborom za moderne React aplikacije.

Testovi performansi pokazuju da je React Hook Form bolji od drugih opcija. Kada nešto upišete u Formik ili druge biblioteke, one se ponovno renderiraju. React Hook Form osigurava da se samo određene komponente ponovno renderiraju. Čak i s puno polja, vaše forme i dalje rade.

Biblioteka ima oko 8KB kada je minificirana i gzipana. Ne ovisi ni o čemu drugom, što održava veličinu bundlea niskom. API površina je mala i laka za razumijevanje. Novim developerima treba samo sati da je nauče, ne dani.

Instalacija

Korištenjem npm-a:

npm install react-hook-form

Korištenjem yarna:

yarn add react-hook-form

Instalirajte potrebne pakete za Material-UI integraciju:

npm install @mui/material @emotion/react @emotion/styled

Za provjeru s validator bibliotekom:

npm install validator
npm install -D @types/validator

Arhitektura Projekta

Da biste bolje organizirali direktorij formi, učinite sljedeće:

src/
├── components/
│   └── Forms/
│       ├── Form/
│       │   └── Form.tsx
│       └── FormInput/
│           └── FormInput.tsx
├── config/
│   ├── form-models.config.ts
│   └── form-names.config.ts
└── utils/
    └── static/
        └── FormValidator.ts

Izrada Wrappera za Prilagođenu Formu

Prilagođena form komponenta uklanja boilerplate kod. Automatski wrappa useForm i FormProvider. React context daje svakoj child komponenti pristup metodama forme.

Ovaj obrazac smanjuje kod koji se ponavlja kroz vašu aplikaciju. Samo jednom trebate definirati strukturu forme, a zatim je možete koristiti bilo gdje. Wrapper se brine o svim povezivanjima iza kulisa.

import { ReactNode } from 'react';
import { DefaultValues, FieldValues, FormProvider, Mode, UseFormReturn, useForm } from 'react-hook-form';

export type FormSubmitHandler<T extends FieldValues> = (data: T, methods: UseFormReturn<T>) => void | Promise<void>;

interface FormProps<T extends FieldValues> {
  children: ReactNode | ((methods: UseFormReturn<T>) => ReactNode);
  onSubmit: FormSubmitHandler<T>;
  defaultValues?: DefaultValues<T>;
  mode?: Mode;
  id?: string;
}

export function Form<T extends FieldValues>({
  children,
  onSubmit,
  defaultValues,
  mode = 'onBlur',
  id,
  ...rest
}: FormProps<T>) {
  const methods = useForm<T>({ mode, defaultValues });

  return (
    <FormProvider {...methods}>
      <form id={id} onSubmit={methods.handleSubmit(data => onSubmit(data, methods))} {...rest}>
        {typeof children === 'function' ? children(methods) : children}
      </form>
    </FormProvider>
  );
}

Submit handler dobiva i vrijednosti forme i methods objekt. Ovo vam omogućuje rukovanje greškama na strani servera s methods.setError(). Render props obrazac čini formState dostupnim za uvjetno renderiranje, poput stanja učitavanja i povratnih informacija o validaciji.

Neki važni propovi:

  • defaultValues – postavlja početno stanje forme
  • mode – postavlja vrijeme za validaciju (onBlur, onChange ili all)
  • id – povezuje eksterne submit gumbe

Izrada FormInput Komponente

FormInput komponenta wrappa MUI TextField u React Hook Form Controller. Dobiva control iz contexta samostalno. Nikada ga ne prosljeđujete kao prop ručno.

Poruke o greškama prikazuju se ispod input polja. Komponenta povezuje stanje forme s UI-jem. Ova apstrakcija čini vaš form kod lakšim za čitanje i razumijevanje.

import { Controller, FieldValues, Path, useFormContext } from 'react-hook-form';

import { TextField, TextFieldProps } from '@mui/material';

interface FormInputProps<T extends FieldValues> extends Omit<TextFieldProps, 'name'> {
  name: Path<T>;
  validate?: (value: any) => true | string;
  renderInput?: (props: { field: any; error?: string }) => ReactNode;
}

export function FormInput<T extends FieldValues>({ name, validate, renderInput, ...props }: FormInputProps<T>) {
  const {
    control,
    formState: { errors },
  } = useFormContext<T>();

  return (
    <Controller
      name={name}
      control={control}
      rules={{ validate }}
      render={({ field }) => {
        if (renderInput) {
          return renderInput({ field, error: errors[name]?.message as string });
        }

        return <TextField {...field} {...props} error={!!errors[name]} helperText={errors[name]?.message as string} />;
      }}
    />
  );
}

renderInput prop vam omogućuje prilagodbu prikaza DatePickera, Autocompletea ili RadioGroup komponenti. Možete dati komponenti bilo koje MUI TextField propove direktno. Oni se šire na input element ispod njih.

Možete koristiti dot notaciju za ugniježđene objekte s name propom. Koristite putanje poput address.city ili user.profile.name. Komponenta ispravno rješava ugniježđene greške.

FormValidator: Centralizirana Validacija

Teško je pratiti logiku validacije koja je raspršena po različitim dijelovima. Ovaj problem može se riješiti korištenjem centralizirane FormValidator klase. Napišite funkcije validacije jednom i koristite ih kroz cijelu aplikaciju.

Svaki validator vraća ili true za validan unos ili string s porukom greške. Ovaj obrazac radi direktno s React Hook Form sustavom validacije. Metoda all povezuje više validatora zajedno.

import Validator from 'validator';

export class FormValidator {
  static all(...fns: Array<(value: any) => true | string>) {
    return (value: any) => fns.map(fn => fn(value)).find(e => e !== true) || true;
  }

  static isNotEmpty = (value: string) => (Validator.isEmpty(value || '') ? 'Obavezno' : true);

  static isNotEmptyArray = (value: any[]) => ((value?.length ?? 0) > 0 ? true : 'Obavezno');

  static isValidEmail = (value: string) => (Validator.isEmail(value || '') ? true : 'Neispravna email adresa');

  static minLength = (min: number) => (value: string) =>
    (value?.length ?? 0) >= min ? true : `Najmanje ${min} znakova`;

  static maxLength = (max: number) => (value: string) =>
    (value?.length ?? 0) <= max ? true : `Maksimalno ${max} znakova`;

  static isWholeNumber = (value: string) => (/^\d+$/.test(value || '') ? true : 'Mora biti cijeli broj');
}

Validator biblioteka ima funkcije validacije koje su testirane u stvarnom svijetu. Brine se o edge slučajevima koje biste mogli propustiti s vlastitim regex obrascima. Instalirajte je s React Hook Formom kako biste osigurali da dobro radi.

Primjeri Korištenja

// Jednostavna validacija
<FormInput name="email" validate={FormValidator.isValidEmail} />

// Kombinirana validacija
<FormInput
  name="email"
  validate={FormValidator.all(
    FormValidator.isNotEmpty,
    FormValidator.isValidEmail
  )}
/>

// Lozinka sa zahtjevima za duljinu
<FormInput
  name="password"
  type="password"
  validate={FormValidator.all(
    FormValidator.isNotEmpty,
    FormValidator.minLength(8),
    FormValidator.maxLength(128)
  )}
/>

// Opcionalno polje - bez validate propa
<FormInput name="nickname" />

Kako vaša aplikacija raste, dodajte više validatora. Struktura klase pomaže da ostanu organizirani i laki za pronalaženje. Članovi tima uvijek mogu pronaći pravila validacije na istom mjestu.

Tipovi Formi

U centralnoj config datoteci definirajte TypeScript sučelja:

// src/config/form-models.config.ts
export type LoginFormValues = {
  email: string;
  password: string;
};

export interface CreateUserFormValues {
  name: string;
  surname: string;
  email: string;
  phoneNumber: string;
}

Pohranite konstante za ID-eve formi eksternih submit gumba:

// src/config/form-names.config.ts
export const LOGIN_FORM = 'login-form';
export const CREATE_USER_FORM = 'create-user-form';

Osnovno Korištenje Forme

Potpuna login forma pokazuje kako svi dijelovi rade zajedno. Počnite s defaultValues objektom koji je istog tipa kao vaš TypeScript tip. Ovo osigurava da je tip siguran od početka.

Form wrapper dobiva defaultne vrijednosti i handler za kada se forma submitta. Child komponente mogu pristupiti stanju forme kroz render propove. isSubmitting zastavica sprječava slanje duplih API poziva.

import { Form, FormInput } from '@/components/Forms';
import { LoginFormValues } from '@/config/form-models.config';
import { AuthService } from '@/services';

const defaultValues: LoginFormValues = {
  email: '',
  password: '',
};

const Login = () => {
  const handleSubmit = async (values: LoginFormValues, methods: UseFormReturn<LoginFormValues>) => {
    const { payload } = await AuthService.login(values);

    if (!payload) {
      methods.setError('password', { message: 'Neispravni podaci za prijavu' });
    }
  };

  return (
    <Form<LoginFormValues> defaultValues={defaultValues} onSubmit={handleSubmit}>
      {({ formState: { isSubmitting } }) => (
        <>
          <FormInput name="email" label="Email" validate={FormValidator.isValidEmail} />
          <FormInput name="password" label="Lozinka" type="password" validate={FormValidator.isNotEmpty} />
          <button type="submit" disabled={isSubmitting}>
            {isSubmitting ? 'Prijava...' : 'Prijavi se'}
          </button>
        </>
      )}
    </Form>
  );
};

setError() prikazuje greške na strani servera. Metoda prima ime polja i konfiguraciju greške. Poruka greške prikazuje se ispod inputa koji ju je uzrokovao. Ova metoda olakšava validaciju i na klijentu i na serveru.

Za projekte koji trebaju biti internacionalizirani, wrapajte poruke grešaka u funkcije prijevoda. Obrazac savršeno radi s i18next i sličnim bibliotekama.

Forme s Modalima i Eksternim Gumbima

U modal dijalozima, submit gumbi obično su u footeru, koji je izvan form elementa. Submit gumb mora biti unutar form taga da HTML forme rade. id prop rješava ovaj problem na pametan način.

Stavite ID na form komponentu. Koristite ovaj ID u form atributu gumba. Preglednik ih automatski povezuje. Ovo možete koristiti s bilo kojom modal bibliotekom ili prilagođenim kodom.

import { CREATE_USER_FORM } from '@/config/form-names.config';

// U tijelu modala
<Form<CreateUserFormValues>
  id={CREATE_USER_FORM}
  defaultValues={defaultValues}
  onSubmit={handleSubmit}
>
  <FormInput name="name" label="Ime" />
  <FormInput name="email" label="Email" />
</Form>

// U footeru modala (izvan forme)
<button type="submit" form={CREATE_USER_FORM}>
  Kreiraj Korisnika
</button>

Stavite ID-eve formi u datoteku konstanti. Ovo sprječava tipfelere i omogućuje refaktoriranje. Uvezite konstantu i u Form komponentu i na mjesto gdje je submit gumb. Obrazac radi za aplikacije s desetcima modal formi.

Renderiranje Prilagođenih Inputa

TextField može obraditi većinu tipova inputa, ali aplikacije trebaju različite tipove inputa. Trebate rukovati DatePickerima, autocomplete padajućim izbornicima i RadioGroupama na poseban način. renderInput prop brine se o ovoj potrebi.

Render funkcija dobiva field objekt i sve greške pronađene tijekom validacije. Stavite field svojstva na svoju komponentu. Za ispravno ažuriranje stanja forme, obradite onChange callback.

// Autocomplete padajući izbornik
<FormInput
  name="role"
  renderInput={({ field, error }) => (
    <Autocomplete
      value={field.value}
      onChange={(_, val) => field.onChange(val)}
      options={roleOptions}
      renderInput={(params) => (
        <TextField
          {...params}
          label="Uloga"
          error={!!error}
          helperText={error}
        />
      )}
    />
  )}
/>

// Date picker
<FormInput
  name="birthDate"
  renderInput={({ field, error }) => (
    <DatePicker
      value={field.value}
      onChange={field.onChange}
      slotProps={{
        textField: {
          error: !!error,
          helperText: error
        }
      }}
    />
  )}
/>

// Radio grupa
<FormInput
  name="gender"
  renderInput={({ field }) => (
    <RadioGroup value={field.value} onChange={field.onChange}>
      <FormControlLabel value="male" control={<Radio />} label="Muško" />
      <FormControlLabel value="female" control={<Radio />} label="Žensko" />
    </RadioGroup>
  )}
/>

Field objekt ima value, onChange, onBlur i ref. value i onChange su sve što većina komponenti treba. Za vremensko usklađivanje validacije, kompleksni dijelovi mogu koristiti onBlur. Obrazac ostaje isti za sve tipove inputa.

Dot Notacija za Ugniježđena Polja

Ugniježđene strukture podataka potrebne su za kompleksne forme. Postoje podobjekti u adresama za dostavu, korisničkim profilima i konfiguracijskim objektima. Dot notacija u imenima polja omogućuje React Hook Formu rukovanje ugniježđivanjem.

Postavite svoje defaultne vrijednosti koristeći ugniježđenu strukturu. U name propu koristite dot putanje. Podaci koji su poslani zadržavaju isti oblik. Nema potrebe za ručnim transformacijama prije API poziva.

interface AddressFormValues {
  name: string;
  address: {
    street: string;
    city: string;
    zipCode: string;
  };
}

const defaultValues: AddressFormValues = {
  name: '',
  address: {
    street: '',
    city: '',
    zipCode: ''
  }
};

// U vašoj form komponenti
<FormInput name="name" label="Puno Ime" />
<FormInput name="address.street" label="Ulica" />
<FormInput name="address.city" label="Grad" />
<FormInput name="address.zipCode" label="Poštanski Broj" />

FormInput komponenta automatski rješava greške koje su ugniježđene. Pristupite dubljim razinama dodavanjem više točaka. Putanje poput user.profile.settings.notifications rade dobro. Obrazac se može koristiti na bilo kojoj razini ugniježđivanja koju vaš model podataka treba.

Korištenje useFormContext

Pristup formi potreban je child komponentama duboko u stablu. Prop drilling metoda forme stvara neuredan kod. useFormContext hook rješava ovaj problem na čist način.

Možete pozvati hook iz bilo koje komponente koja je unutar FormProvidera. Vraća sve metode forme, kao što su watch, setValue, getValues i reset. Za pravilno autocomplete, tipizirajte hook s vašim sučeljem vrijednosti forme.

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

interface OrderFormValues {
  customerId: string;
  locationId: string;
}

const CustomerLocationInput = () => {
  const { watch, setValue } = useFormContext<OrderFormValues>();
  const customerId = watch('customerId');

  const handleChange = (newId: string) => {
    setValue('customerId', newId);
    setValue('locationId', ''); // Resetiraj ovisno polje
  };

  return <CustomerSelect value={customerId} onChange={handleChange} />;
};

Ovaj obrazac dobro radi s kaskadnim padajućim izbornicima. Resetirajte child polja kada se promijeni odabir roditelja. Funkcija setValue mijenja određena polja bez mijenjanja drugih. Za stvaranje kompleksne ovisne logike, pratite više polja.

Stavite grupe inputa koje se mogu ponovno koristiti u vlastite komponente. One pristupaju stanju forme kroz context. Roditeljska forma ostaje jednostavna za korištenje. Ovaj obrazac značajno poboljšava organizaciju koda.

Rukovanje Greškama na Strani Servera

Klijentska validacija pronalazi očite pogreške. Serverska validacija brine se o poslovnim pravilima i ograničenjima baze podataka. Prikažite serverske greške na istom mjestu kao i klijentske greške. Bez obzira odakle greška dolazi, korisnici uvijek dobivaju istu povratnu informaciju.

Metoda setError dodaje greške u kod. Pozovite je nakon što dobijete API odgovore. Poruka greške prikazuje se ispod input polja koje ju je uzrokovalo. Ispravite polje i ponovno ga pošaljite da obrišete grešku.

const handleSubmit = async (values: CreateUserFormValues, methods: UseFormReturn<CreateUserFormValues>) => {
  const { payload, errors } = await UserService.create(values);

  if (!payload) {
    // Greške specifične za polje
    if (errors?.email) {
      methods.setError('email', {
        type: 'server',
        message: errors.email,
      });
    }

    if (errors?.username) {
      methods.setError('username', {
        type: 'server',
        message: 'Korisničko ime je zauzeto',
      });
    }

    return;
  }

  // Rukovanje uspjehom
  showToast('Korisnik uspješno kreiran');
};

Greške koje nisu specifične za polje trebaju izgledati drugačije. Možete koristiti toast obavijesti ili error banner na razini forme. root error ključ u React Hook Formu brine se o porukama na razini forme. Odaberite metodu koja odgovara vašim UI obrascima.

Stanje Forme za Korisničko Iskustvo

formState objekt ima korisne informacije u sebi. Koristite ga za poboljšanje korisničkog iskustva dodavanjem stanja učitavanja, upozorenja za nespremljene promjene i uvjetnih stanja gumba.

  • isSubmitting – postaje true kada je slanje asinkrono. Onemogućite inpute i gumbe tako da korisnici ne mogu napraviti isti zahtjev dva puta. Koristite indikatore učitavanja za prikaz napretka.
  • isDirty – provjerava jesu li vrijednosti u formi različite od defaultnih. Obavijestite korisnike prije odlaska o promjenama koje nisu spremljene. Dopustite korisnicima spremanje samo kada postoje promjene.
  • isValid – pokazuje trenutno stanje validacije. Koristite ga s drugim zastavicama za preciznu kontrolu gumba.
<Form<UserFormValues> defaultValues={defaultValues} onSubmit={handleSubmit}>
  {({ formState: { isSubmitting, isDirty, isValid } }) => (
    <>
      <FormInput name="name" label="Ime" />
      <FormInput name="email" label="Email" />

      <button type="submit" disabled={isSubmitting || !isDirty || !isValid}>
        {isSubmitting ? 'Spremanje...' : 'Spremi'}
      </button>

      <button type="button" onClick={() => methods.reset()} disabled={!isDirty}>
        Resetiraj
      </button>
    </>
  )}
</Form>

U onChange modu, forma se stalno provjerava. Pristupite touched i dirty stanjima za svako polje za detaljniju UI povratnu informaciju.

Najbolje Prakse

  • Uvijek postavite defaultValues na pravi TypeScript tip
  • Za najbolje korisničko iskustvo koristite mode="onBlur"
  • Tipizirajte svoje useFormContext pozive
  • Koristite FormValidator.all() za kombiniranje validatora
  • Koristite form ID za gumbe koji submittaju modale
  • Koristite setError() za rukovanje serverskim greškama
  • Koristite isSubmitting za provjeru učitava li se stranica
  • Pratite isDirty za dijaloge s promjenama koje još nisu spremljene

Spremni za razgovor?

Pošaljite kratki uvod kako bismo dogovorili uvodni poziv. Poziv se fokusira na vaše izazove i ciljeve te ocrtava prve korake prema pravom digitalnom rješenju.

React Hook Form Kompletan Vodič: Validacija, TypeScript i Više | Workspace