Menu Zamknij
27 listopada 2021

Jak obsługiwać formularze w React? Biblioteka Formik.

Spis treści

Podziel się wpisem ze znajomymi!

Wstęp

Do obsługi formularzy w React najczęściej używamy bibliotek React-Hook-Form oraz Formika. W tym artykule skupimy się na Formiku oraz omówieniu sposobów, jak zoptymalizować swoją pracę z tą biblioteką.

Co powinniśmy poprawić w swoim kodzie?
Jedną z rzeczy, która niesamowicie irytuje użytkownika to zresetowanie stanu formularza po nieumyślnym odświeżeniu przez niego strony (w tym artykule poruszono jeszcze 9 innych denerwujących userów rzeczy). Jestem w stanie wyobrazić sobie frustrację użytkownika wypełniającego formularz z ponad 10 polami, który przez przeładowanie strony zresetuje się do stanu początkowego.

W artykule tym przedstawię nie tylko, jak temu zapobiec, ale też zwrócę uwagę na to, jak ważna jest komunikacja z użytkownikiem podczas wypełniania formularzy. Zaczynajmy!

Tworzenie własnego formularza

Na początku zaczniemy od zainstalowania Reacta w terminalu i stworzenia nowej aplikacji za pomocą create-react-app:

npx create-react-app

Create-react-app stworzy nam podstawowy boilerplate aplikacji. Zacznijmy od stworzenia formularza. Zainstalujmy więc bibliotekę formik. W terminalu wpisz:

npm install formik

i pobierz bibliotekę do projektu.

Teraz przejdźmy już do samego kodu.

Poniżej plik App.js:

// import bibliotek

import { Field, FormikProvider, useFormik } from "formik";

import { useCallback, useEffect } from "react";

// kod hooka useLocalStorageState znajdziesz poniżej

import { useLocalStorageState } from "./hook/hook";

// klucz formularza

const LOCAL_STORAGE_KEY = "customLocalStorageKey";

// wartości jakie przyjmuje formularz

const INITIAL_VALUES = { input1: "", input2: [] };

function App() {

   // wykorzystanie custom hooka

  const [initialValues, handleUpdateForm] = useLocalStorageState({

    key: LOCAL_STORAGE_KEY,

    value: INITIAL_VALUES,

  });

  // obsługa wysyłki formularza

  const handleSubmit = useCallback((values) => {

    console.log("Submitting form!!!!", values);

  }, []);

  // konfiguracja formika

  const formik = useFormik({

    enableReinitialize: true, // odblokowanie możliwości reinicjalizacji, dzięki temu updatując initialValues wartości w inputach się zmienią

    initialValues: initialValues, // wartości początkowe

    onSubmit: handleSubmit, // funkcja wykonująca się po zatwierdzeniu formularza

  });

  // zapis wartości formularza do localstorage na zmianę wartości formika

  useEffect(() => {

    handleUpdateForm(formik.values);

  }, [formik.values, handleUpdateForm]);

  // reset wartości w localstorage dla naszego formularza

  const handleReset = useCallback(() => {

    handleUpdateForm(INITIAL_VALUES);

  }, [handleUpdateForm]);

  return (

    <FormikProvider value={formik}>

      <form onSubmit={formik.handleSubmit}>

        <h2>Form state:</h2>

        <pre>{JSON.stringify(formik.values, null, 2)}</pre>

        <h2>LocalStorage:</h2>

        <pre>{localStorage.getItem(LOCAL_STORAGE_KEY)}</pre>

        <h2>My form</h2>

        <label>

          <Field type="radio" name="input1" value="one" />

          One

        </label>

        <label>

          <Field type="radio" name="input1" value="two" />

          Two

        </label>

        <label>

          <Field type="checkbox" name="input2" value="Red" />

          Red

        </label>

        <label>

          <Field type="checkbox" name="input2" value="Blue" />

          Blue

        </label>

        <button type="submit">Submit</button>

        <button type="reset" onClick={handleReset}>

          Reset

        </button>

      </form>

    </FormikProvider>

  );

}

export default App;

Poniżej Hook do obsługi zapisywania stanu formularza do pamięci:

import { useCallback, useState } from "react";

export const useLocalStorageState = ({ key, value }) => {

    // sprawdzamy czy cos w localStorage istnieje pod danym kluczem

    const parsedLocalStorage = JSON.parse(localStorage.getItem(key) || "{}");

    

    // używamy istniejącej lub przekazanej do hooka wartości

    const initialValue = Object.keys(parsedLocalStorage).length > 0 ? parsedLocalStorage : value;

 

    // stworzenie stanu do obserwacji zmian wartosci formularza

    const [localStorageState, setLocalStorageState] = useState(initialValue);

    // umozliwienie aktualizacji localstorage

    const handleUpdateLocalStorageState = useCallback(

    (x) => {

      setLocalStorageState(x);

      localStorage.setItem(key, JSON.stringify(x));

    }, [key]

  );

  // enkapsulacja danych dostep do stanu w localstorage i aktualizacji

  return [localStorageState, handleUpdateLocalStorageState];

};

Obecny efekt to otrzymanie formularza, który przechowuje swój stan nawet po zrestartowaniu przeglądarki. Bloki form state oraz localstorage są tylko do podglądu, w realnym przykładzie należy go usunąć.

Teraz skupmy się na komunikacji z użytkownikiem. Zawsze wszystkie dane powinny być walidowane na front-endzie i na back-endzie, aby zapobiec błędom. Podczas wypełniania przez użytkownika formularza należy go informować o błędzie i zapobiec wysyłce formularze, jeśli źle go wypełnił. Do walidacji formularza można używać wyrażeń regularnych, ale o wiele łatwiej jest zainstalować bibliotekę służącą do walidacji - yup.

npm install yup

Po zainstalowaniu zmienimy initial_values i klucz identyfikujący formularz

const LOCAL_STORAGE_KEY = "newsletterForm";

const INITIAL_VALUES = { name: "", age: "", email:"" };

... oraz pola dla niego

<label>

     Name:

    <Field type="text" name="name" />

</label>

<label>

    Age:

    <Field type="numbr" name="age" />

</label>

<label>

    Email:

    <Field type="text" name="email" />

</label>

Stwórzmy teraz schemat walidacji do formularza za pomocą yupa

const validationSchema = yup.object().shape({

  name: yup.string().required(),

  age: yup.number().positive().required(),

  email: yup.string().email().required(),

});

  1. Schemat walidacji sprawdza, czy dane pod podanym kluczem są odpowiedniego typu (metody string(), number()).
  2. Metoda required() pozwala na sprawdzenie, czy pole jest wypełnione.
  3. Metoda positive() pozwala na sprawdzenie, czy typ number jest liczbą dodatnią.

Teraz musimy podpiąć schemat walidacji do Formika.

const formik = useFormik({

    enableReinitialize: true,

    initialValues: initialValues,

    validationSchema: validationSchema,

    onSubmit: (values) => {

      handleSubmit(values);

    },

});

Obecnie nasz formularz nie przejdzie walidacji, jeśli użytkownik nie spełni określonych warunków.

Brakuje nam tutaj jednak poinformowania go o tym, co źle robi i co powinien poprawić. Pamiętaj, że komunikaty te nie powinny obwiniać użytkownika, tylko mu pomóc.

Przykładowo komunikat “Pole nie jest emailem” powinien brzmieć “Podaj prawidłowy e-mail np. welcome@onet.pl”. Dzięki dostępowi do obiektu formik, możemy sprawdzić, czy ma on jakieś errory i czy został już przez użytkownika dotknięty, Nie chcemy, żeby użytkownik widział błędy pod polami, których jeszcze nie wypełnił.

<label>

     Name:

     <Field type="text" name="name" />

    { formik.errors.name && formik.touched.name ? (

    <div>{formik.errors.name}</div>

     ) : null}

</label>

<label>

     Age:

     <Field type="number" name="age" />

     { formik.errors.age && formik.touched.age ? (

     <div>{formik.errors.age}</div>

      ) : null}

</label>

<label>

      Email:

      <Field type="text" name="email" />

      { formik.errors.email && formik.touched.email ? (

      <div>{formik.errors.email}</div>

       ) : null}

 </label>

Po kliknięciu w pole submit, pojawił się komunikat, iż pole email jest wymagane:

Gdybyś chciał dostosować komunikat, to Yup bez problemu pozwala nam na przekazywanie własnych stringów do walidatorów:

const validationSchema = yup.object().shape({

  name: yup.string().required("Imie jest wymagane"),

  age: yup.number("Wiek nie może zawierać liter").positive("Wpisz wiek dodatni, np.25").required("Wiek jest wymagany"),

  email: yup.string().email("Podaj prawidłowy emial np. welcome@onet.pl").required("Email jest wymagany"),

});

Poniżej możemy ujrzeć już dostosowany komunikat:

Stylowanie formularza pozostawiam Ci już Twojej inwencji twórczej. Pamiętaj jednak, że błędy powinny wyświetlać się na czerwono, aby zwrócić uwagę użytkownika.

Jako dodatkowe wyzwanie spróbuj podłączyć podany formularz do wybranej przez siebie biblioteki komponentów, np. Material UI.

Kod finalny: 

import { Field, FormikProvider, useFormik } from "formik";

import { useCallback, useEffect } from "react";

import { useLocalStorageState } from "./hook/hook";

import * as yup from "yup";

const validationSchema = yup.object().shape({

  name: yup.string().required("Imie jest wymagane"),

  age: yup.number("Wiek nie może zawierać liter").positive("Wpisz wiek dodatni, np.25").required("Wiek jest wymagany"),

  email: yup.string().email("Podaj prawidłowy emial np. welcome@onet.pl").required("Email jest wymagany"),

});

const LOCAL_STORAGE_KEY = "newsletterForm";

const INITIAL_VALUES = { name: "", age: 0, email: "" };

function App() {

  const [initialValues, handleUpdateForm] = useLocalStorageState({

    key: LOCAL_STORAGE_KEY,

    value: INITIAL_VALUES,

  });

  const handleSubmit = useCallback((values) => {

    console.log("Submitting form!!!!", values);

  }, []);

  const formik = useFormik({

    enableReinitialize: true,

    initialValues: initialValues,

    validationSchema: validationSchema,

    onSubmit: (values) => {

      handleSubmit(values);

    },

  });

  useEffect(() => {

    handleUpdateForm(formik.values);

  }, [formik.values, handleUpdateForm]);

  const handleReset = useCallback(() => {

    handleUpdateForm(INITIAL_VALUES);

  }, [handleUpdateForm]);

  return (

    <FormikProvider value={formik}>

      <form onSubmit={formik.handleSubmit}>

        <h2>Form state:</h2>

        <pre>{JSON.stringify(formik.values, null, 2)}</pre>

        <h2>LocalStorage:</h2>

        <pre>{localStorage.getItem(LOCAL_STORAGE_KEY)}</pre>

        <h2>My form</h2>

        <div>

          <label>

            Name:

            <Field type="text" name="name" />

            {formik.errors.name && formik.touched.name ? (

              <div>{formik.errors.name}</div>

            ) : null}

          </label>

        </div>

        <div>

          <label>

            Age:

            <Field type="number" name="age" />

            {formik.errors.age && formik.touched.age ? (

              <div>{formik.errors.age}</div>

            ) : null}

          </label>

        </div>

        <div>

          <label>

            Email:

            <Field type="text" name="email" />

            { formik.errors.email && formik.touched.email ? (

              <div>{formik.errors.email}</div>

            ) : null}

          </label>

        </div>

        <button type="submit">Submit</button>

        <button type="reset" onClick={handleReset}>

          Reset

        </button>

      </form>

    </FormikProvider>

  );

}

export default App;

Hook:

import { useCallback, useState } from "react";

export const useLocalStorageState = ({ key, value }) => {

    // sprawdzamy czy cos w localStorage istnieje pod danym kluczem

    const parsedLocalStorage = JSON.parse(localStorage.getItem(key) || "{}");

    // używamy istniejącej lub przekazanej do hooka wartości

    const initialValue = Object.keys(parsedLocalStorage).length > 0 ? parsedLocalStorage : value;

    // stworzenie stanu do obserwacji zmian wartości formularza

    const [localStorageState, setLocalStorageState] = useState(initialValue);

    // umożliwienie aktualizacji localstorage

    const handleUpdateLocalStorageState = useCallback(

        (x) => {

            setLocalStorageState(x);

            localStorage.setItem(key, JSON.stringify(x));

        }, [key]

  );

  // enkapsulacja danych dostęp do stanu w localstorage i aktualizacji

  return [localStorageState, handleUpdateLocalStorageState];

};

Sprawdź również nasz system mentorowania i outsourcowania specjalistów
  • Wyznaczona ścieżka od A do Z w kierunku przebranżowienia
  • Indywidualnie dostosowany materiał pod ucznia
  • Code Review ze strony mentora
  • Budowa portfolio
  • Intensywna praca z materiałami szkoleniowymi