0.11.1 • Published 7 days ago

safe-form v0.11.1

Weekly downloads
-
License
MIT
Repository
github
Last release
7 days ago

safe-form

NPM Version Github License NPM Downloads

⚡️ 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>
  )
}

License

MIT

0.11.0

7 days ago

0.10.1

7 days ago

0.11.1

7 days ago

0.10.0

7 days ago

0.9.0

8 days ago

0.8.1

9 days ago

0.7.2

9 days ago

0.8.0

9 days ago

0.7.1

9 days ago

0.8.2

8 days ago

0.7.3

9 days ago

0.7.0

9 days ago

0.5.3

10 days ago

0.6.1

9 days ago

0.6.0

10 days ago

0.2.6

10 days ago

0.5.0

10 days ago

0.4.0

10 days ago

0.3.1

10 days ago

0.5.2

10 days ago

0.5.1

10 days ago

0.2.1

12 days ago

0.2.0

12 days ago

0.2.3

12 days ago

0.2.2

12 days ago

0.2.5

12 days ago

0.2.4

12 days ago

0.1.0

2 months ago