|
| 1 | +# @mtayyabrawan/formhook |
| 2 | + |
| 3 | +A lightweight React hook for managing forms with built-in Zod validation. Simplify form state, validation, and submission handling in React and Next.js applications. |
| 4 | + |
| 5 | +## Features |
| 6 | + |
| 7 | +- **React Hook API**: `useForm<T>()` provides a simple interface for form state management. |
| 8 | +- **Zod Validation**: Integrates seamlessly with Zod schemas for type-safe validation. |
| 9 | +- **Type Safety**: Full TypeScript support with inferred types for form values and errors. |
| 10 | +- **Flexible API**: Supports field-level access via `formField`, error handling via `formErrors`, and easy reset functionality. |
| 11 | +- **Next.js Ready**: Works out of the box with Server Components and App Router. |
| 12 | + |
| 13 | +## Installation |
| 14 | + |
| 15 | +```bash |
| 16 | +npm install @mtayyabrawan/formhook |
| 17 | +# or |
| 18 | +yarn add @mtayyabrawan/formhook |
| 19 | +``` |
| 20 | + |
| 21 | +## Peer Dependencies |
| 22 | + |
| 23 | +- React (^18 or ^19) |
| 24 | +- Zod (^3.25.0 or ^4) |
| 25 | + |
| 26 | +Make sure these are installed in your project. |
| 27 | + |
| 28 | +## Basic Usage |
| 29 | + |
| 30 | +```tsx |
| 31 | +import { useForm } from "@mtayyabrawan/formhook"; |
| 32 | +import { z } from "zod"; |
| 33 | + |
| 34 | +const schema = z.object({ |
| 35 | + name: z.string().min(3, "Name is required"), |
| 36 | + email: z.string().email("Invalid email"), |
| 37 | + age: z.number().int().min(18, "Must be 18+"), |
| 38 | +}); |
| 39 | + |
| 40 | +export default function UserForm() { |
| 41 | + const { reset, formField, formErrors, handleSubmit } = useForm({ |
| 42 | + initialData: { name: "", email: "", age: 0 }, |
| 43 | + validationSchema: schema, |
| 44 | + }); |
| 45 | + |
| 46 | + const onSubmit = (data: { name: string; email: string; age: number }) => { |
| 47 | + console.log("Form submitted:", data); |
| 48 | + }; |
| 49 | + |
| 50 | + return ( |
| 51 | + <form onSubmit={handleSubmit(onSubmit)}> |
| 52 | + <div> |
| 53 | + <label> |
| 54 | + Name |
| 55 | + <input type="text" {...formField("name")} /> |
| 56 | + </label> |
| 57 | + {formErrors.name && ( |
| 58 | + <p className="text-red-500">{formErrors.name}</p> |
| 59 | + )} |
| 60 | + </div> |
| 61 | + |
| 62 | + <div> |
| 63 | + <label> |
| 64 | + Email |
| 65 | + <input type="email" {...formField("email")} /> |
| 66 | + </label> |
| 67 | + {formErrors.email && ( |
| 68 | + <p className="text-red-500">{formErrors.email}</p> |
| 69 | + )} |
| 70 | + </div> |
| 71 | + |
| 72 | + <div> |
| 73 | + <label> |
| 74 | + Age |
| 75 | + <input type="number" {...formField("age")} /> |
| 76 | + </label> |
| 77 | + {formErrors.age && ( |
| 78 | + <p className="text-red-500">{formErrors.age}</p> |
| 79 | + )} |
| 80 | + </div> |
| 81 | + |
| 82 | + <button type="submit">Submit</button> |
| 83 | + <button type="button" onClick={reset}> |
| 84 | + Reset |
| 85 | + </button> |
| 86 | + </form> |
| 87 | + ); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +### Key API Elements |
| 92 | + |
| 93 | +- **`useForm<T>(props: UseFormProps<T>)`**: Returns an object with form state and methods. |
| 94 | +- **`formField<K extends keyof T>(fieldName: K)`**: Accessor for a specific field, returns `value`, `name`, and `onChange`. |
| 95 | +- **`formErrors: FormErrors<T>`**: Object containing validation errors keyed by field name. |
| 96 | +- **`reset()`**: Resets form to initial data and clears errors. |
| 97 | +- **`handleSubmit(callback: (data: T) => void)`**: Wraps submit handler to trigger validation and provide `event.preventDefault()`. |
| 98 | + |
| 99 | +### Using with Next.js App Router |
| 100 | + |
| 101 | +The hook works seamlessly with Next.js 13+ App Router. Since it's a client-side hook, wrap your form component in a `"use client"` directive: |
| 102 | + |
| 103 | +```tsx |
| 104 | +"use client"; |
| 105 | + |
| 106 | +import { useForm } from "@mtayyabrawan/formhook"; |
| 107 | +import { z } from "zod"; |
| 108 | + |
| 109 | +const schema = z.object({ |
| 110 | + username: z.string().min(3), |
| 111 | + password: z.string().min(8), |
| 112 | +}); |
| 113 | + |
| 114 | +export default function LoginForm() { |
| 115 | + const { formField, formErrors, handleSubmit } = useForm({ |
| 116 | + initialData: { username: "", password: "" }, |
| 117 | + validationSchema: schema, |
| 118 | + }); |
| 119 | + |
| 120 | + const onSubmit = (data: { username: string; password: string }) => { |
| 121 | + // Handle login logic |
| 122 | + }; |
| 123 | + |
| 124 | + return ( |
| 125 | + <form onSubmit={handleSubmit(onSubmit)}> |
| 126 | + <input {...formField("username")} /> |
| 127 | + {formErrors.username && <p>{formErrors.username}</p>} |
| 128 | + |
| 129 | + <input type="password" {...formField("password")} /> |
| 130 | + {formErrors.password && <p>{formErrors.password}</p>} |
| 131 | + |
| 132 | + <button type="submit">Login</button> |
| 133 | + </form> |
| 134 | + ); |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +## FAQ |
| 139 | + |
| 140 | +**Q: Is the hook compatible with React 19?** |
| 141 | +A: Yes, it supports React 18 and 19. |
| 142 | + |
| 143 | +**Q: How do I reset the form programmatically?** |
| 144 | +A: Call the `reset` method returned by `useForm`. |
| 145 | + |
| 146 | +## License |
| 147 | + |
| 148 | +MIT © [Muhammad Tayyab](https://linkedin.com/in/mtayyabrawan) |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +_Happy form building!_ 🚀 |
0 commit comments