safe-form
v0.11.2
Published
⚡️ End-to-end type-safety from client to server.
Downloads
27
Maintainers
Readme
safe-form
⚡️ End-to-end type-safety from client to server. Inspired by react-hook-form and next-safe-action.
Features
- ✅ Ridiculously easy to use
- ✅ 100% type-safe
- ✅ Input validation using zod
- ✅ Server error handling
- ✅ Automatic input binding
- ✅ Native file upload support
Requirements
Install
npm install safe-form
Usage
First, define your schema in a separate file, so you can use it both in the form and in the server action:
schema.ts
import { z } from 'zod'
export const exampleSchema = z.object({
name: z.string().min(3, 'Name must be at least 3 characters'),
message: z.string().min(10, 'Message must be at least 10 characters'),
attachment: z.instanceof(File).nullish()
})
Now, create a server action:
action.ts
'use server'
import { createFormAction, FormActionError } from 'safe-form'
import { exampleSchema } from './schema'
export const exampleAction = createFormAction(exampleSchema, async (input) => {
if (input.attachment && input.attachment.size >= 1024 * 1024 * 10) {
throw new FormActionError('The maximum file size is 10MB.') // Custom errors! 💜
}
return `Hello, ${input.name}! Your message is: ${input.message}.`
})
Finally, create a form as a client component:
form.tsx
'use client'
import { useForm } from 'safe-form'
import { exampleAction } from './action'
import { exampleSchema } from './schema'
export const HelloForm = () => {
const { connect, bindField, isPending, error, fieldErrors, response } =
useForm({
action: exampleAction,
schema: exampleSchema
})
return (
<form {...connect()}>
<label htmlFor='name'>Name</label>
<input {...bindField('name')} />
{fieldErrors.name && <pre>{fieldErrors.name.first}</pre>}
<br />
<label htmlFor='message'>Message</label>
<textarea {...bindField('message')} />
{fieldErrors.message && <pre>{fieldErrors.message.first}</pre>}
<br />
<label htmlFor='attachment'>Attachment (optional)</label>
<input type='file' {...bindField('attachment')} />
{fieldErrors.attachment && <pre>{fieldErrors.attachment.first}</pre>}
<br />
<button type='submit' disabled={isPending}>
Submit
</button>
<br />
{error && <pre>{error}</pre>}
{response && <div>{response}</div>}
</form>
)
}