UNPKG

next-form-action

Version:

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

120 lines (115 loc) 3.78 kB
import React, { useRef, useActionState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; class ActionResponse extends Error { payload; success; formErrors; extra; redirect; refresh; constructor(message, success, params = {}) { super(message); this.success = success; this.formErrors = params.formErrors; this.extra = params.extra; this.redirect = params.redirect; this.refresh = params.refresh; } toResponse(formData) { return { payload: formData, success: this.success, message: this.message, formErrors: this.formErrors, extra: this.extra, redirect: this.redirect, refresh: this.refresh }; } } class ActionError extends ActionResponse { constructor(message, params = {}) { super(message, false, params); } } class ActionSuccess extends ActionResponse { constructor(message, params = {}) { super(message, true, params); } } const createActionState = (payload = new FormData(), params = {}) => { return { ...params, payload }; }; function error(message, params = {}) { throw new ActionError(message, params); } function success(message, params = {}) { throw new ActionSuccess(message, params); } const createAction = (context, handler) => { return async (state, formData) => { try { return await handler(state, formData); } catch (error2) { if (error2 instanceof ActionResponse) { return error2.toResponse(formData); } if (error2 && typeof error2 === "object" && "digest" in error2 && typeof error2.digest === "string" && error2.digest.startsWith("NEXT_")) { throw error2; } console.error(`Error in form action "${context}":`, error2); const message = error2 instanceof Error && error2.message ? error2.message : "An unexpected error occurred. Please try again."; return new ActionError(message).toResponse(formData); } }; }; const useAction = (action, actionState) => { const router = useRouter(); const formRef = useRef(null); const [state, dispatch, isPending] = useActionState(action, actionState || createActionState()); const onSuccessCallbackRef = useRef(null); const onErrorCallbackRef = useRef(null); const onSubmitCallbackRef = useRef(null); useEffect(() => { if (state.success === true && onSuccessCallbackRef.current) { onSuccessCallbackRef.current(state); onSuccessCallbackRef.current = null; } else if (state.success === false && state.message && onErrorCallbackRef.current) { onErrorCallbackRef.current(state); onErrorCallbackRef.current = null; } if (state.redirect) router.push(state.redirect); if (state.refresh) router.refresh(); }, [state, router]); const FormError = (...props) => { return state.message ? /* @__PURE__ */ React.createElement("div", { ...props }, state.message) : null; }; const Form = ({ children, ...props }) => /* @__PURE__ */ React.createElement("form", { ...props, ref: formRef, action: enhancedDispatch }, children); const onFormSuccess = (callback) => { onSuccessCallbackRef.current = callback; }; const onFormError = (callback) => { onErrorCallbackRef.current = callback; }; const onFormSubmit = (callback) => { onSubmitCallbackRef.current = callback; }; const enhancedDispatch = async (formData) => { if (onSubmitCallbackRef.current) { await onSubmitCallbackRef.current(formData); onSubmitCallbackRef.current = null; } dispatch(formData); }; return { Form, FormError, state, isPending, formRef, onFormSuccess, onFormError, onFormSubmit }; }; export { ActionError, ActionResponse, ActionSuccess, createAction, createActionState, error, success, useAction };