next-form-action
Version:
A TypeScript library for handling form actions in Next.js applications
120 lines (115 loc) • 3.78 kB
JavaScript
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 };