Skip to Content
FrontendReactIntermediate2.5 React Forms (Intermediate)

Forms (Intermediate) 📝

Forms capture user intent. Organize state, validation, and submissions carefully to keep UX smooth.

1) Form State Patterns 🔄

Controlled Fields

  • Store every input value in React state.
  • Pros: predictable, easy validation; cons: more updates.
const [form, setForm] = useState({ name: "", email: "" }); function handleChange(event) { const { name, value } = event.target; setForm(prev => ({ ...prev, [name]: value })); }

Reducer-driven Forms

  • Use useReducer when state transitions become complex (multi-step forms, dynamic lists).
function reducer(state, action) { switch (action.type) { case "update": return { ...state, [action.name]: action.value }; case "reset": return initialState; default: return state; } }

Uncontrolled with refs

  • Useful for forms that submit once without live validation or for third-party widgets.

2) Validation Strategies ✅❌

  • Client-side synchronous: check inputs on change (e.g., regex, required fields).
  • Server-side: trust backend to confirm final data; display errors returned from APIs.
  • Schema-based: use libraries like Zod or Yup to define constraints once and share across client/server.
import { z } from "zod"; const schema = z.object({ email: z.string().email(), password: z.string().min(8) }); function handleSubmit(event) { event.preventDefault(); const result = schema.safeParse(form); if (!result.success) setErrors(result.error.format()); }

3) Form Libraries 🧰

React Hook Form

  • Registers inputs via refs, minimizing re-renders.
import { useForm } from "react-hook-form"; const { register, handleSubmit, formState } = useForm(); <form onSubmit={handleSubmit(console.log)}> <input {...register("email", { required: "Email required" })} /> {formState.errors.email && <span>{formState.errors.email.message}</span>} <button type="submit">Send</button> </form>

Formik

  • Manages values, touched, errors; integrates nicely with Yup.
import { Formik, Form, Field, ErrorMessage } from "formik"; <Formik initialValues={{ email: "" }} onSubmit={values => console.log(values)} > <Form> <Field name="email" /> <ErrorMessage name="email" /> <button type="submit">Save</button> </Form> </Formik>

Choose based on team familiarity and performance needs. React Hook Form generally yields fewer re-renders; Formik offers a declarative API.

4) File Uploads 📁

  • File inputs must remain uncontrolled; capture the file via refs or event targets.
  • Use FormData when sending files to APIs.
function AvatarUploader() { const [file, setFile] = useState(null); async function handleSubmit(event) { event.preventDefault(); if (!file) return; const formData = new FormData(); formData.append("avatar", file); await fetch("/api/avatar", { method: "POST", body: formData }); } return ( <form onSubmit={handleSubmit}> <input type="file" accept="image/*" onChange={event => setFile(event.target.files?.[0] ?? null)} /> <button type="submit">Upload</button> </form> ); }
  • Show previews (URL.createObjectURL) and progress bars (XHR or fetch progress events).

Key Takeaways ✅

  • Controlled state suits dynamic validation; reducers tame complex flows.
  • Use schema validation to unify rules across client and server.
  • React Hook Form and Formik handle boilerplate; pick based on ergonomics and performance.
  • File uploads need special handling with FormData and often progress indicators.

Recap 🔄

Intermediate forms juggle state, validation, libraries, and uploads. Choose the right state pattern, enforce validation consistently, leverage form helpers when complexity spikes, and treat file inputs distinctly to keep UX robust.

Last updated on