Saat kita mengerjakan form pada halaman web, kita perlu mendefinisikan value-value yang akan dikirim oleh form nanti, misalnya Email dan Password.
<form>
<div>
<label>Email</label>
<input type="email" name="email" />
</div>
<div>
<label>Password</label>
<input type="password" name="password" />
</div>
<button type="submit">
Submit
</button>
</form>
Di React, kita bisa melakukannya seperti ini. Kita bisa menggunakan useState hook untuk menyimpan value yang diperlukan untuk dikirim oleh form.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const submit = () => {
// Akses ke email dan password diperlukan disini
// untuk kemudian dikirim ke server melalui REST API.
// Selain itu perlu juga untuk menampilkan semacam alert
// saat kita berhasil atau error.
};
return (
<form>
<div>
<label>Email</label>
<input
type="email"
name="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</div>
<div>
<label>Password</label>
<input
type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
</div>
<button type="submit" onClick={submit}>
Submit
</button>
</form>
);
Tapi ada masalah disini. Jika ada banyak value yang perlu didefinisikan, maka kode akan jadi seperti ini.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [address, setAddress] = useState("");
const [province, setProvince] = useState("");
const [district, setDistrict] = useState("");
const [subdistrict, setSubdistrict] = useState("");
const [postalCode, setPostalCode] = useState("");
// Kode value-value lainnya disini misalnya.
return (
<form>
<div>
<label>Email</label>
<input
type="email"
name="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
/>
</div>
<div>
<label>Password</label>
<input
type="password"
name="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
/>
</div>
<!-- Kode JSX input-input lainnya disini misalnya. -->
<button type="submit" onClick={submit}>
Submit
</button>
</form>
);
Belum lagi kita perlu untuk melakukan validasi terhadap value-value yang dimasukan kedalam form untuk mencegah value-value yang tidak valid bisa terkirim ke backend dan juga perlu menampilkan pesan error dibawah input yang bersangkutan ketika value nya tidak valid. Dan mungkin perlu juga untuk me-reset form ketika form berhasil dikirim. Jadi banyak sekali memang hal yang harus diurus ketika membuat sebuah form.
Solusi
Disini Formik memberikan solusi untuk urusan pembuatan form di React. Mulai dari mendefinisikan value untuk form, validasi value, dan lain sebagainya dan form tidak akan dikirim jika masih ada value yang tidak valid.
const form = useFormik({
initialValues: {
email: "",
password: "",
},
validate: (values) => {
const { email, password } = values;
const errors = {};
if (!email) {
errors.email = "Email wajib ada";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
) {
errors.email = "Format email tidak valid";
}
// Validasi value lainnya disini.
return errors;
},
onSubmit: async (values, actions) => {
try {
const { email, password } = values;
actions.setSubmitting(true);
// Ini cuman function dummy.
await submitToServer({
email,
password,
});
actions.resetForm();
// Tampilkan alert berhasil disini.
} catch (error) {
// Tampilkan alert error disini.
} finally {
actions.setSubmitting(false);
}
},
});
return (
<form>
<div>
<label>Email</label>
<input
type="email"
name="email"
value={form.values.email}
onChange={form.handleChange}
/>
{!!form.errors.email && <p>{form.errors.email}</p>}
</div>
<div>
<label>Password</label>
<input
type="password"
name="password"
value={form.values.password}
onChange={form.handleChange}
/>
{!!form.errors.password && <p>{form.errors.password}</p>}
</div>
<button type="submit" onClick={() => form.handleSubmit()}>
Submit
</button>
</form>
);
Untuk validasi yang lebih baik, bisa kita kombinasikan Formik dengan validation schema seperti Yup atau Zod untuk menggantikan validate function.
import { useFormik } from "formik";
import { z } from "zod";
import { toFormikValidationSchema } from "zod-formik-adapter";
const signInSchema = z.object({
email: z
.string({
required_error: "Email wajib ada",
})
.email("Format email tidak valid"),
password: z.string({
required_error: "Password wajib ada",
}),
});
const form = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: toFormikValidationSchema(signInSchema),
onSubmit: async (values, actions) => {
try {
const { email, password } = values;
actions.setSubmitting(true);
// Ini cuman function dummy.
await submitToServer({
email,
password,
});
actions.resetForm();
// Tampilkan alert berhasil disini.
} catch (error) {
// Tampilkan alert error disini.
} finally {
actions.setSubmitting(false);
}
},
});
return (
<form>
<div>
<label>Email</label>
<input
type="email"
name="email"
value={form.values.email}
onChange={form.handleChange}
/>
{!!form.errors.email && <p>{form.errors.email}</p>}
</div>
<div>
<label>Password</label>
<input
type="password"
name="password"
value={form.values.password}
onChange={form.handleChange}
/>
{!!form.errors.password && <p>{form.errors.password}</p>}
</div>
<button type="submit" onClick={() => form.handleSubmit()}>
Submit
</button>
</form>
);
Kesimpulan
Di artikel ini kita telah mempelajari bagaimana kombinasi Formik dan Zod dapat membuat struktur kode terkait pembuatan form menjadi lebih rapi, lebih sedikit kode, dan lebih mudah untuk diimplementasikan untuk membuat form yang baik.