react-form-action 2.15.1 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,7 @@ End-to-end typesafe success, error & validation state control for Next.js form a
24
24
  - ✅ Binds the action with the arguments from the _args_ prop.
25
25
  - ✅ Has an optional `validate` prop to validate the form data client-side before calling the server action.
26
26
  - ✅ Enables you to reset the action state with the `reset()` callback.
27
+ - ✅ `useResetAction()` hook (or `<ResetAction />` component) to automatically initialize your action when the hook/component unmounts.
27
28
 
28
29
  **Context-bound `<Form />` component**
29
30
 
@@ -335,6 +336,44 @@ export function SignUpAction({ children }: PropsWithChildren) {
335
336
  }
336
337
  ```
337
338
 
339
+ #### Automatic action state reset (when unmounting component)
340
+
341
+ Works with Next.js routing, where routes are wrapped in `<Activity />`.
342
+
343
+ ```tsx
344
+ "use client";
345
+
346
+ import type { PropsWithChildren } from "react";
347
+ import { Action, ResetAction } from "react-form-action/client";
348
+
349
+ import { updateUser } from "./action";
350
+
351
+ export function UpdateUserAction({ children }: PropsWithChildren) {
352
+ return (
353
+ <Action
354
+ action={updateUser}
355
+ initialData={undefined}
356
+ >
357
+ {/* IMPORTANT: must be rendered as a child of the <Action /> context. */}
358
+ <ResetAction />
359
+ {children}
360
+ </Action>
361
+ );
362
+ }
363
+ ```
364
+
365
+ When you prefer hooks to JSX, you can use the `useResetAction()` usually in your forms:
366
+
367
+ ```tsx
368
+ import { Form, useResetAction } from "react-form-action/client";
369
+
370
+ function MyForm() {
371
+ useResetAction();
372
+
373
+ return <Form>...</Form>
374
+ }
375
+ ```
376
+
338
377
 
339
378
  ### `<Form>` Component
340
379
 
package/dist/client.d.mts CHANGED
@@ -2,19 +2,26 @@ import { a as FormAction, c as SuccessState, i as FailureState, o as InitialStat
2
2
  import React$1, { JSX, PropsWithChildren } from "react";
3
3
  import { $ZodErrorTree } from "zod/v4/core";
4
4
  //#region src/Action.d.ts
5
- type ActionProps<Data, Error, ValidationError, Arguments extends unknown[] = []> = PropsWithChildren<Arguments extends [unknown, ...unknown[]] ? {
5
+ type ActionProps<Data, Error, ValidationError, Arguments extends unknown[] = []> = PropsWithChildren<{
6
+ /**
7
+ * Whether the form action should automatically reset when unmounted.
8
+ */
9
+ autoReset?: boolean;
10
+ /**
11
+ * A function to validate the form data before submission.
12
+ * When the function returns a ValidationError value, the action state will turn to "invalid".
13
+ * Return nullable value, to proceed with the submission.
14
+ */
15
+ validate?: (payload: FormData) => ValidationError | undefined | null;
16
+ permalink?: string;
17
+ initialData: Data;
18
+ } & (Arguments extends [unknown, ...unknown[]] ? {
6
19
  action: FormAction<Data, Error, ValidationError, FormData, Arguments>;
7
20
  args: Arguments;
8
- initialData: Data;
9
- permalink?: string;
10
- validate?: (payload: FormData) => null | ValidationError;
11
21
  } : {
12
22
  action: FormAction<Data, Error, ValidationError, FormData, Arguments>;
13
- initialData: Data;
14
23
  args?: undefined;
15
- permalink?: string;
16
- validate?: (payload: FormData) => null | ValidationError;
17
- }>;
24
+ })>;
18
25
  type ActionStatusFlags<T extends ActionState<unknown, unknown, unknown>["type"] | unknown = unknown> = {
19
26
  isInitial: T extends "initial" ? true : false;
20
27
  isInvalid: T extends "invalid" ? true : false;
@@ -40,13 +47,13 @@ type SpreadActionContext<Data = unknown, Error = unknown, ValidationError = unkn
40
47
  * A hook to consume the form action state from the context.
41
48
  */
42
49
  declare function useActionContext<Data, Error, ValidationError, Args extends unknown[] = []>(_action?: FormAction<Data, Error, ValidationError, FormData, Args>): SpreadActionContext<Data, Error, ValidationError>;
43
- declare function Action<Data, Error, ValidationError, Args extends unknown[] = []>({ children, action: formAction, initialData, args, permalink, validate }: ActionProps<Data, Error, ValidationError, Args>): import("react").JSX.Element;
50
+ declare function Action<Data, Error, ValidationError, Args extends unknown[] = []>({ children, action: formAction, initialData, args, permalink, validate, autoReset }: ActionProps<Data, Error, ValidationError, Args>): import("react").JSX.Element;
44
51
  //#endregion
45
52
  //#region src/ZodFieldError.d.ts
46
53
  declare const SEPARATOR: ".";
47
54
  type Paths<Shape extends $ZodErrorTree<object>, Props = Required<NonNullable<Shape["properties"]>>> = { [k in keyof Props]: k extends string ? NonNullable<Props[k]> extends $ZodErrorTree<object> ? "properties" extends keyof NonNullable<Props[k]> ? `${k}${typeof SEPARATOR}${Paths<NonNullable<Props[k]>>}` : k : k : never; }[keyof Props];
48
55
  type InferZodErrorPaths<T> = T extends $ZodErrorTree<object> ? Paths<T> : never;
49
- type ZodFieldErrorProps<Errors extends $ZodErrorTree<any>, Name extends "" | InferZodErrorPaths<Errors>> = {
56
+ type ZodFieldErrorProps<Errors extends $ZodErrorTree<unknown>, Name extends "" | InferZodErrorPaths<Errors>> = {
50
57
  errors: Errors;
51
58
  name: Name;
52
59
  children?: (props: ZodFieldErrorChildrenProps<Name>) => React$1.ReactNode;
@@ -123,4 +130,14 @@ declare function Pending({ children }: PropsWithChildren | {
123
130
  }) => React.ReactNode;
124
131
  }): import("react").ReactNode;
125
132
  //#endregion
126
- export { Action, ActionContextState, ActionProps, Form, FormProps, InferZodErrorPaths, Paths, Pending, SpreadActionContext, ZodFieldError, ZodFieldErrorChildrenProps, ZodFieldErrorProps, createComponents, noError, useActionContext };
133
+ //#region src/useResetAction.d.ts
134
+ /**
135
+ * Hook to automatically reset the action when the component unmounts, unless it is in the initial state.
136
+ */
137
+ declare function useResetAction(): void;
138
+ /**
139
+ * Component that automatically resets the action when it unmounts, unless it is in the initial state.
140
+ */
141
+ declare function ResetAction(): null;
142
+ //#endregion
143
+ export { Action, ActionContextState, ActionProps, Form, FormProps, InferZodErrorPaths, Paths, Pending, ResetAction, SpreadActionContext, ZodFieldError, ZodFieldErrorChildrenProps, ZodFieldErrorProps, createComponents, noError, useActionContext, useResetAction };
package/dist/client.mjs CHANGED
@@ -1 +1 @@
1
- "use client";import{n as e,t}from"./createFormAction-BwMYAzm2.mjs";import{createContext as n,startTransition as r,use as i,useActionState as a,useCallback as o}from"react";import{Fragment as s,jsx as c}from"react/jsx-runtime";const l={isInitial:!1,isInvalid:!1,isFailure:!1,isSuccess:!1},u=n(null);function d(e){let t=i(u);if(!t)throw Error(`ActionContext must be initialized before use. Is your useActionContext hook wrapped with an <Action> Component?`);return t}function f({children:n,action:i,initialData:s,args:d,permalink:f,validate:p}){let m=d?i.bind(null,...d):i,[h,g,_]=a(async(n,r)=>{if(r===null)return e(s);if(p){let e=await t(({invalid:e,success:t})=>async(n,r)=>{let i=p(r);return i?e(i):t(!0)})(n,r);if(e.type===`invalid`)return e}return m(n,r)},e(s),f),v=o(()=>{r(()=>{g(null)})},[g]),y=h.type===`initial`?{...h,...l,action:g,reset:v,isPending:_,isInitial:!0}:h.type===`invalid`?{...h,...l,action:g,reset:v,isPending:_,isInvalid:!0}:h.type===`failure`?{...h,...l,action:g,reset:v,isPending:_,isFailure:!0}:{...h,...l,action:g,reset:v,isPending:_,isSuccess:!0};return c(u,{value:y,children:n})}const p=Object.freeze({errors:Object.freeze([])});function m({errors:e,name:t,children:n=({error:e})=>c(s,{children:e})}){if(t.length===0)return n({name:t,error:e.errors[0],errors:e.errors});let r=t.split(`.`),i=e;for(let e of r){if(!i.properties){i=p;break}i=i.properties[e]??p}return n({name:t,error:i.errors[0],errors:i.errors})}function h(e,t={}){function n({name:n,children:r}){let i=d(e),{isInvalid:a,validationError:o}=i,l=r??(({error:e})=>a&&c(s,{children:e}));return c(m,{errors:o??p,name:n,children:e=>{let n=Object.fromEntries(Object.entries(t.fieldProps??{}).map(([t,n])=>[t,n(i,e)]));return l({...e,...n})}})}function r({children:t}){let{isFailure:n,error:r}=d(e);return n?typeof t==`function`?t({isFailure:n,error:r}):t:null}function i({children:t}){let{isSuccess:n,data:r}=d(e);return typeof t==`function`?t({isSuccess:n,data:r}):n&&t}function a({children:t}){let{isInvalid:n,validationError:r}=d(e);return typeof t==`function`?t({isInvalid:n,validationError:r}):n&&t}return{FieldError:n,Failure:r,Success:i,Invalid:a}}function g({autoReset:e=!1,...t}){let{action:n}=d();return c(`form`,{...e?{action:n}:{onSubmit:e=>{e.preventDefault();let t=e.currentTarget;r(()=>{n(new FormData(t))})}},...t})}function _({children:e}){let{isPending:t}=d();return typeof e==`function`?e({isPending:t}):t&&e}export{f as Action,g as Form,_ as Pending,m as ZodFieldError,h as createComponents,p as noError,d as useActionContext};
1
+ "use client";import{n as e,t}from"./createFormAction-BwMYAzm2.mjs";import{createContext as n,startTransition as r,use as i,useActionState as a,useCallback as o,useEffect as s}from"react";import{Fragment as c,jsx as l,jsxs as u}from"react/jsx-runtime";function d(){let{reset:e,isInitial:t}=h();s(()=>()=>{t||e()},[t,e])}function f(){return d(),null}const p={isInitial:!1,isInvalid:!1,isFailure:!1,isSuccess:!1},m=n(null);function h(e){let t=i(m);if(!t)throw Error(`ActionContext must be initialized before use. Is your useActionContext hook wrapped with an <Action> Component?`);return t}function g({children:n,action:i,initialData:s,args:c,permalink:d,validate:h,autoReset:g}){let _=c?i.bind(null,...c):i,[v,y,b]=a(async(n,r)=>{if(r===null)return e(s);if(h){let e=await t(({invalid:e,success:t})=>async(n,r)=>{let i=h(r);return i?e(i):t(!0)})(n,r);if(e.type===`invalid`)return e}return _(n,r)},e(s),d),x={action:y,reset:o(()=>{r(()=>{y(null)})},[y]),isPending:b},S=v.type===`initial`?{...x,...v,...p,isInitial:!0}:v.type===`invalid`?{...x,...v,...p,isInvalid:!0}:v.type===`failure`?{...x,...v,...p,isFailure:!0}:{...x,...v,...p,isSuccess:!0};return u(m,{value:S,children:[g&&l(f,{}),n]})}const _=Object.freeze({errors:Object.freeze([])});function v({errors:e,name:t,children:n=({error:e})=>l(c,{children:e})}){if(t.length===0)return n({name:t,error:e.errors[0],errors:e.errors});let r=t.split(`.`),i=e;for(let e of r){if(!i.properties){i=_;break}i=i.properties[e]??_}return n({name:t,error:i.errors[0],errors:i.errors})}function y(e,t={}){function n({name:n,children:r}){let i=h(e),{isInvalid:a,validationError:o}=i,s=r??(({error:e})=>a&&l(c,{children:e}));return l(v,{errors:o??_,name:n,children:e=>{let n=Object.fromEntries(Object.entries(t.fieldProps??{}).map(([t,n])=>[t,n(i,e)]));return s({...e,...n})}})}function r({children:t}){let{isFailure:n,error:r}=h(e);return n?typeof t==`function`?t({isFailure:n,error:r}):t:null}function i({children:t}){let{isSuccess:n,data:r}=h(e);return typeof t==`function`?t({isSuccess:n,data:r}):n&&t}function a({children:t}){let{isInvalid:n,validationError:r}=h(e);return typeof t==`function`?t({isInvalid:n,validationError:r}):n&&t}return{FieldError:n,Failure:r,Success:i,Invalid:a}}function b({autoReset:e=!1,...t}){let{action:n}=h();return l(`form`,{...e?{action:n}:{onSubmit:e=>{e.preventDefault();let t=e.currentTarget;r(()=>{n(new FormData(t))})}},...t})}function x({children:e}){let{isPending:t}=h();return typeof e==`function`?e({isPending:t}):t&&e}export{g as Action,b as Form,x as Pending,f as ResetAction,v as ZodFieldError,y as createComponents,_ as noError,h as useActionContext,d as useResetAction};
package/dist/index.d.mts CHANGED
@@ -6,6 +6,6 @@ import { ZodObject, z } from "zod";
6
6
  * @param schema z.object(...) schema
7
7
  * @returns validate function
8
8
  */
9
- declare function validateSchema<T extends ZodObject>(schema: T): (formData: FormData) => z.core.$ZodErrorTree<z.TypeOf<T>, string> | null;
9
+ declare function validateSchema<T extends ZodObject>(schema: T): (formData: FormData) => z.core.$ZodErrorTree<z.infer<T>, string> | null;
10
10
  //#endregion
11
11
  export { ActionState, FailureState, Flatten, FormAction, InitialState, InvalidState, SuccessState, createFormAction, formAction, initial, validateSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-action",
3
- "version": "2.15.1",
3
+ "version": "2.17.0",
4
4
  "description": "State management helpers for the react form actions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,7 +19,7 @@
19
19
  "lint:write": "biome lint ./src --write",
20
20
  "format": "biome format --write .",
21
21
  "format:check": "biome check .",
22
- "ncu": "ncu --interactive --format group",
22
+ "check-updates": "ncu --interactive --format group",
23
23
  "pub:next": "npm publish --tag next"
24
24
  },
25
25
  "exports": {
@@ -57,22 +57,22 @@
57
57
  "zod-form-data": "^3.0.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@biomejs/biome": "^2.5.10",
60
+ "@biomejs/biome": "^2.5.11",
61
61
  "@testing-library/dom": "^10.4.1",
62
62
  "@testing-library/jest-dom": "^7.0.1",
63
- "@testing-library/react": "^16.3.2",
63
+ "@testing-library/react": "^16.3.3",
64
64
  "@testing-library/user-event": "^14.6.6",
65
65
  "@types/react": "^19.2.18",
66
66
  "@types/react-dom": "^19.2.5",
67
- "@vitejs/plugin-react": "^6.1.0",
68
- "happy-dom": "^20.11.8",
67
+ "@vitejs/plugin-react": "^6.1.1",
68
+ "happy-dom": "^20.12.0",
69
69
  "npm-check-updates": "^23.1.0",
70
70
  "react": "^19.2.8",
71
71
  "react-dom": "^19.2.8",
72
72
  "tsdown": "^0.22.14",
73
73
  "typescript": "^7.0.2",
74
74
  "vitest": "^4.1.11",
75
- "zod": "^4.4.3",
75
+ "zod": "^4.5.4",
76
76
  "zod-form-data": "^3.0.3"
77
77
  }
78
78
  }