Installation
pnpm add @hookraft/useformuseForm
import { useForm } from "@hookraft/useform"const form = useForm({
fields: {
email: {
value: "",
rules: [
{ required: true, message: "Email is required" },
{ pattern: /^\S+@\S+\.\S+$/, message: "Invalid email address" },
],
},
password: {
value: "",
rules: [
{ required: true, message: "Password is required" },
{ minLength: 8, message: "Must be at least 8 characters" },
],
},
},
onSubmit: async (values) => await loginUser(values),
onSuccess: () => console.log("Logged in!"),
onError: (err) => console.error("Failed:", err),
})The problem it solves
Every form in React makes you write the same boilerplate — field state, error state, loading state, validation logic, submit handling.
// Without useForm
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [isSubmitting, setIsSubmitting] = useState(false)
// With useForm
const form = useForm({
fields: {
email: { value: "", rules: [{ required: true }] },
password: { value: "", rules: [{ required: true }] },
},
})How it works
Each field exposes everything you need directly:
form.fields.email.value
form.fields.email.error
form.fields.email.touched
form.fields.email.isDirty
form.fields.email.isValid
form.fields.email.disabledConnecting inputs:
<input
value={form.fields.email.value as string}
onChange={(e) => form.setValue("email", e.target.value)}
onBlur={() => form.setTouched("email")}
/>
{form.fields.email.error && <p>{form.fields.email.error}</p>}Full Example
import { useForm } from "@hookraft/useform"
function LoginForm() {
const form = useForm({
fields: {
email: {
value: "",
rules: [
{ required: true, message: "Email is required" },
{ pattern: /^\S+@\S+\.\S+$/, message: "Enter a valid email" },
],
},
password: {
value: "",
rules: [
{ required: true, message: "Password is required" },
{ minLength: 8, message: "Must be at least 8 characters" },
],
},
},
validateOn: "blur",
onSubmit: async (values) => {
await fetch("/api/login", {
method: "POST",
body: JSON.stringify(values),
})
},
})
return (
<form onSubmit={form.handleSubmit}>
<input
value={form.fields.email.value as string}
onChange={(e) => form.setValue("email", e.target.value)}
onBlur={() => form.setTouched("email")}
/>
{form.fields.email.error && <p>{form.fields.email.error}</p>}
<input
type="password"
value={form.fields.password.value as string}
onChange={(e) => form.setValue("password", e.target.value)}
onBlur={() => form.setTouched("password")}
/>
{form.fields.password.error && <p>{form.fields.password.error}</p>}
<button type="submit" disabled={form.isSubmitting}>
{form.isSubmitting ? "Logging in..." : "Login"}
</button>
</form>
)
}Reset Example
Use a ref to call reset() from inside onSuccess:
import { useRef } from "react"
import { useForm } from "@hookraft/useform"
function ContactForm() {
const formRef = useRef<useForm.Return<any>>(null!)
const form = useForm({
fields: {
name: { value: "" },
email: { value: "" },
message: { value: "" },
},
onSubmit: async () => {},
onSuccess: () => {
formRef.current.reset()
},
})
formRef.current = form
return (
<form onSubmit={form.handleSubmit}>
<input
value={form.fields.name.value as string}
onChange={(e) => form.setValue("name", e.target.value)}
/>
</form>
)
}Validation Rules
rules: [
{ required: true, message: "Required" },
{ minLength: 3, message: "Too short" },
{ maxLength: 50, message: "Too long" },
{ min: 0, message: "Must be positive" },
{ max: 100, message: "Must be 100 or less" },
{ pattern: /^\S+@\S+\.\S+$/, message: "Invalid email" },
{
validate: (value, allValues) => {
if (value === allValues.username) return "Cannot match username"
return true
},
},
]Options
| Prop | Type | Default |
|---|---|---|
fields | useForm.FieldsConfig | - |
onSubmit | (values: useForm.FormValues) => Promise<void> | void | - |
onSuccess | (values: useForm.FormValues) => void | - |
onError | (error: unknown, values: useForm.FormValues) => void | - |
onChange | (values: useForm.FormValues, fieldName: string) => void | - |
validateOn | "change" | "blur" | "submit" | "blur" |
Returns
| Prop | Type | Default |
|---|---|---|
fields | { [K in keyof T]: useForm.FieldState } | - |
values | useForm.FormValues | - |
errors | useForm.FormErrors | - |
status | useForm.Status | - |
is(status) | (s: useForm.Status) => boolean | - |
isValid | boolean | - |
isDirty | boolean | - |
isSubmitting | boolean | - |
isSuccess | boolean | - |
isError | boolean | - |
submitError | unknown | - |
setValue(name, value) | (name: keyof T, value: useForm.FieldValue) => void | - |
setTouched(name) | (name: keyof T) => void | - |
setError(name, error) | (name: keyof T, error: string) => void | - |
clearError(name) | (name: keyof T) => void | - |
validateField(name) | (name: keyof T) => boolean | - |
validateAll() | () => boolean | - |
submit() | () => Promise<void> | - |
handleSubmit(e?) | (e?: React.FormEvent) => Promise<void> | - |
reset() | () => void | - |
resetField(name) | (name: keyof T) => void | - |