When we create a form for web page, we need to define any values that form need to send later like Email and 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>
In React, we can do it like this. We can use useState hook to save value that form need to send.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const submit = () => {
// Access to email and password is needed here
// for later will be send to the server via REST API.
// Besides that, we need to show alert when submit success or 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>
);
But there's a problem here. If there's any values that need to be defined, then the code will be like this.
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("");
// Other values useState here.
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>
<!-- Other JSX inputs here. -->
<button type="submit" onClick={submit}>
Submit
</button>
</form>
);
Yet we need to validate values that we insert into form to prevent any invalid values sent to the backend and we need to show error message under corresponding input when value is invalid. And maybe we need to reset form after form successfully sent. There are so many things that we need to care about when we create a form.
Solution
Here Formik give a solution for handling form in React. Starts from defining values for form, validation, and many others and form will not sent if there's still invalid values exists.
const form = useFormik({
initialValues: {
email: "",
password: "",
},
validate: (values) => {
const { email, password } = values;
const errors = {};
if (!email) {
errors.email = "Email is required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
) {
errors.email = "Email format is invalid";
}
// Other validation here.
return errors;
},
onSubmit: async (values, actions) => {
try {
const { email, password } = values;
actions.setSubmitting(true);
// This is only dummy function.
await submitToServer({
email,
password,
});
actions.resetForm();
// Show success alert here.
} catch (error) {
// Show error alert here.
} 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>
);
For better validation, we can combine Formik with validation schema like Yup or Zod to replace 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 is required",
})
.email("Email format is invalid"),
password: z.string({
required_error: "Password is required",
}),
});
const form = useFormik({
initialValues: {
email: "",
password: "",
},
validationSchema: toFormikValidationSchema(signInSchema),
onSubmit: async (values, actions) => {
try {
const { email, password } = values;
actions.setSubmitting(true);
// This is only dummy function.
await submitToServer({
email,
password,
});
actions.resetForm();
// Show success alert here.
} catch (error) {
// Show error alert here.
} 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>
);
Conclusion
In this article we learned how combination of Formik and Zod can make code structure regarding form creation become cleaner, less code, and easy to implement for creating a better form.