UNPKG

next-form-action

Version:

A TypeScript library for handling form actions in Next.js applications

381 lines (272 loc) 9.95 kB
# next-form-action A TypeScript library for handling form actions in Next.js applications with enhanced developer experience and type safety. ## Features - 🚀 **Type-safe form actions** with full TypeScript support - 🎯 **Built-in state management** for form submissions - 🔄 **Automatic redirects and refresh** handling - 🎨 **React hooks** for seamless integration - 📝 **Form validation** and error handling - ⚡ **Next.js App Router** optimized - 🌐 **Dual module support** (ESM + CommonJS) - 🔧 **Lifecycle callbacks** for submit, success, and error events - 🛡️ **Next.js error handling** for redirects and system errors - ✨ **Global error/success functions** for clean action code ## Installation ```bash npm install next-form-action # or yarn add next-form-action # or pnpm add next-form-action ``` ## Quick Start ### 1. Create a Form Action ```typescript import { createAction, error, success } from 'next-form-action'; export const loginAction = createAction('login', async (state, formData) => { const email = formData.get('email') as string; const password = formData.get('password') as string; // Validate input if (!email || !password) { error('Email and password are required'); } try { // Your authentication logic here const user = await authenticate(email, password); success('Login successful!', { redirect: '/dashboard', }); } catch (err) { error('Invalid credentials'); } }); ``` **Note:** The library automatically handles Next.js system errors like `redirect()` and `notFound()`, so you can use them directly in your actions without additional error handling. ### 2. Use in Your Component ```tsx 'use client'; import { useAction } from 'next-form-action'; import { loginAction } from './actions'; export default function LoginForm() { const { Form, FormError, state, isPending } = useAction(loginAction); return ( <Form className="space-y-4"> <div> <input name="email" type="email" placeholder="Email" required className="w-full p-2 border rounded" /> </div> <div> <input name="password" type="password" placeholder="Password" required className="w-full p-2 border rounded" /> </div> <FormError className="text-red-500" /> <button type="submit" disabled={isPending} className="w-full p-2 bg-blue-500 text-white rounded disabled:opacity-50"> {isPending ? 'Logging in...' : 'Login'} </button> </Form> ); } ``` ## API Reference ### `createAction(context, handler)` Creates a form action with built-in error handling and state management. **Parameters:** - `context` (string): A descriptive name for the action (used for logging) - `handler` (function): The action function that handles form submission **Action Function Parameters:** - `state` (ActionState): Current form state - `formData` (FormData): Form data from submission ### `error(message, params?)` Throws an error response to terminate action execution with an error state. **Parameters:** - `message` (string | null): Error message to display - `params` (ActionParams, optional): Additional parameters like formErrors, redirect, etc. ### `success(message, params?)` Throws a success response to terminate action execution with a success state. **Parameters:** - `message` (string | null): Success message to display - `params` (ActionParams, optional): Additional parameters like redirect, refresh, etc. ### `useAction(action)` React hook for managing form state and submission. **Returns:** - `Form`: Pre-configured form component - `FormError`: Component to display error messages - `state`: Current form state - `isPending`: Boolean indicating if form is submitting - `formRef`: Ref of the form element - `onFormSubmit`: Register callback for form submission (before processing) - `onFormSuccess`: Register callback for successful submissions - `onFormError`: Register callback for failed submissions ### Types #### `ActionState` ```typescript type ActionState = { payload?: FormData; success: boolean; message: string | null; formErrors?: Record<string, string[]>; extra?: Record<string, unknown>; redirect?: string; refresh?: boolean; }; ``` #### `ActionParams` ```typescript type ActionParams = Omit<ActionState, 'payload' | 'success' | 'message'>; ``` ## Advanced Usage ### Complete Lifecycle Management ```tsx 'use client'; import { useAction } from 'next-form-action'; import { createUserAction } from './actions'; export default function CreateUserForm() { const { Form, FormError, isPending, onFormSubmit, onFormSuccess, onFormError } = useAction(createUserAction); // Called immediately when form is submitted onFormSubmit(formData => { console.log('📤 Submitting user creation...'); analytics.track('user_creation_started', { email: formData.get('email'), }); }); // Called when action completes successfully onFormSuccess(state => { console.log('✅ User created successfully!'); toast.success(state.message); router.push('/users'); }); // Called when action fails onFormError(state => { console.log('❌ User creation failed'); toast.error(state.message); analytics.track('user_creation_failed'); }); return ( <Form className="space-y-4"> <input name="name" placeholder="Name" required /> <input name="email" type="email" placeholder="Email" required /> <FormError className="text-red-500" /> <button type="submit" disabled={isPending} className="btn-primary"> {isPending ? 'Creating...' : 'Create User'} </button> </Form> ); } ``` ### Next.js System Error Handling The library automatically handles Next.js system errors like `redirect()`, `notFound()`, and other framework-level errors: ```typescript import { createAction, error, success } from 'next-form-action'; import { redirect, notFound } from 'next/navigation'; export const userAction = createAction('user', async (state, formData) => { const userId = formData.get('userId') as string; // These Next.js errors are automatically handled if (!userId) { notFound(); // Will trigger Next.js 404 page } const user = await updateUser(userId, formData); if (user.needsVerification) { redirect('/verify'); // Will redirect properly } success('User updated successfully!'); }); ``` ### Custom Success/Error Handling ```tsx 'use client'; import { useAction } from 'next-form-action'; import { submitAction } from './actions'; export default function AdvancedForm() { const { Form, state, isPending, onFormSuccess, onFormError } = useAction(submitAction); onFormSuccess(state => { console.log('Form submitted successfully!', state); // Custom success logic }); onFormError(state => { console.error('Form submission failed:', state); // Custom error handling }); return <Form>{/* Your form content */}</Form>; } ``` ### Form Validation with Multiple Errors ```typescript import { createAction, error, success } from 'next-form-action'; export const signupAction = createAction('signup', async (state, formData) => { const formErrors: Record<string, string[]> = {}; const email = formData.get('email') as string; const password = formData.get('password') as string; if (!email) { formErrors.email = ['Email is required']; } else if (!isValidEmail(email)) { formErrors.email = ['Please enter a valid email']; } if (!password) { formErrors.password = ['Password is required']; } else if (password.length < 8) { formErrors.password = ['Password must be at least 8 characters']; } if (Object.keys(formErrors).length > 0) { error('Please fix the errors below', { formErrors }); } // Process signup... success('Account created successfully!', { redirect: '/welcome', }); }); ``` ### Redirect and Refresh ```typescript import { createAction, success } from 'next-form-action'; export const updateProfileAction = createAction('updateProfile', async (state, formData) => { // Update logic... success('Profile updated!', { refresh: true, // Refresh the current page }); }); export const deleteItemAction = createAction('deleteItem', async (state, formData) => { // Delete logic... success('Item deleted!', { redirect: '/items', // Redirect to items list }); }); ``` ## Key Features ### Global Error/Success Functions The library now provides global `error()` and `success()` functions that can be imported and used directly in your actions, making the code cleaner and more intuitive: ```typescript import { createAction, error, success } from 'next-form-action'; export const myAction = createAction('example', async (state, formData) => { if (!formData.get('required-field')) { error('Required field is missing'); // Throws and terminates execution } // Do your logic... success('Operation completed successfully!'); // Throws and terminates execution }); ``` The `error()` and `success()` functions work like `throw` statements - they immediately terminate the action execution and return the appropriate response to the client. ## Requirements - Next.js 15+ (App Router) - React 19+ - TypeScript 5+ ## Development This project includes several utility scripts to help with development and publishing: ### Development Scripts ```bash # Run all quality checks pnpm run check # Build the package pnpm run build # Type check pnpm run type-check # Lint and format pnpm run lint pnpm run lint:fix pnpm run format:check ``` ## Contributing Contributions are welcome! Please feel free to submit a Pull Request. ## License MIT © [Mattia Malonni](https://github.com/mattiamalonni) ## Links - [GitHub Repository](https://github.com/mattiamalonni/next-form-action) - [Issues](https://github.com/mattiamalonni/next-form-action/issues) - [npm Package](https://www.npmjs.com/package/next-form-action)