react-form-action 1.2.2 → 1.3.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
@@ -19,6 +19,11 @@ End-to-end typesafe success, error & validation state control for Next.js 14 for
19
19
  - ✅ `<Form />` component reads the action's response.
20
20
  - ✅ Computes progress meta-state like `isInvalid`, `isSuccess` and more.
21
21
 
22
+ **Context**
23
+
24
+ - ✅ Access the action state via `useFormContext()` hook.
25
+ - ✅ Keeps JSX markup free of render-props.
26
+
22
27
  ## Install
23
28
 
24
29
  ```
@@ -206,6 +211,50 @@ export function UpdateUserForm() {
206
211
  }
207
212
  ```
208
213
 
214
+ ### Context Form
215
+
216
+ ```tsx
217
+ // Example route: /app/auth/signup/SignUpForm.tsx
218
+ "use client";
219
+
220
+ import { createForm, Pending, useFormContext } from "react-form-action/client";
221
+ import { signupAction } from "./actions";
222
+
223
+ // This call will succeed only in client component
224
+ const { Form } = createForm(signupAction);
225
+
226
+ function Error() {
227
+ // read any state from the FormContext:
228
+ const {
229
+ error,
230
+ data,
231
+ validationError,
232
+ isPending,
233
+ isFailure,
234
+ isInvalid,
235
+ isSuccess,
236
+ isInitial
237
+ } = useFormContext();
238
+
239
+ return isFailure && "Failed to submit". // use the error somehow
240
+ }
241
+
242
+ // render this form on your RSC page (/app/auth/signup/page.tsx)
243
+ export function SignupForm() {
244
+ // This Form does not use render props.
245
+ return (
246
+ <Form>
247
+ <input name="email" />
248
+ <input name="password" />
249
+ <Pending>
250
+ {/* This renders only when the action is pending. */}
251
+ <p>Please wait...</p>
252
+ </Pending>
253
+ </Form>
254
+ );
255
+ }
256
+ ```
257
+
209
258
  ### `<ZodFieldError>` Component
210
259
 
211
260
  Actions created with `formAction` builder will have the `validationError` of [`ZodFormattedError`](https://zod.dev/ERROR_HANDLING?id=formatting-errors) type.
package/dist/client.d.mts CHANGED
@@ -1,14 +1,30 @@
1
- import * as React from 'react';
2
- import React__default, { FormHTMLAttributes } from 'react';
1
+ import * as react from 'react';
2
+ import react__default, { PropsWithChildren, FormHTMLAttributes } from 'react';
3
+ import { c as FormAction, F as FormState, b as InitialState, I as InvalidState, a as FailureState, S as SuccessState } from './createFormAction-252fadef.js';
3
4
  import { RenderProp } from 'react-render-prop-type';
4
- import { F as FormState, b as InitialState, I as InvalidState, a as FailureState, S as SuccessState } from './createFormAction-3a51310f.js';
5
5
  import { useFormStatus } from 'react-dom';
6
6
  import { ZodFormattedError } from 'zod';
7
7
 
8
+ /**
9
+ * Creates a Form component which state is accessible via context - useFormContext() hook.
10
+ */
11
+ declare function createForm<Data, Error = Data, ValidationError = Record<string, never>>(action: FormAction<Data, Error, ValidationError>): {
12
+ Form: ({ children, ...props }: PropsWithChildren<{
13
+ initialData: Data;
14
+ }>) => react__default.JSX.Element;
15
+ };
16
+
8
17
  type FormStateProps<Data, Error, ValidationError, Payload> = {
9
18
  action: (state: FormState<Data, Error, ValidationError>, payload: Payload) => Promise<FormState<Data, Error, ValidationError>>;
10
19
  initialData: Data;
11
20
  permalink?: string;
21
+ /**
22
+ * Opt-in into automatic form reset by using the form "action" prop.
23
+ * By default, the onSubmit with a custom transition which opts-out of the implicit form reset.
24
+ * See for more. https://github.com/facebook/react/issues/29034
25
+ * @default false
26
+ */
27
+ autoReset?: boolean;
12
28
  };
13
29
  declare function initial<Data>(data: Data): InitialState<Data>;
14
30
  type FormStatusFlags<T extends FormState<unknown, unknown, unknown>["type"] | unknown = unknown> = {
@@ -21,9 +37,24 @@ type FormMetaState<T extends FormState<unknown, unknown, unknown>> = T & FormSta
21
37
  isPending: boolean;
22
38
  };
23
39
  type FormProps<Data, Error, ValidationError> = Omit<FormHTMLAttributes<HTMLFormElement>, "action" | "children"> & FormStateProps<Data, Error, ValidationError, FormData> & RenderProp<FormMetaState<InitialState<Data>> | FormMetaState<InvalidState<ValidationError>> | FormMetaState<FailureState<Error>> | FormMetaState<SuccessState<Data>>>;
24
- declare function Form<Data, Error, ValidationError>({ children, action, initialData, permalink, ...props }: FormProps<Data, Error, ValidationError>): React__default.JSX.Element;
40
+ declare function Form<Data, Error, ValidationError>({ children, action, initialData, permalink, autoReset, ...props }: FormProps<Data, Error, ValidationError>): react__default.JSX.Element;
41
+
42
+ /**
43
+ * A context exposing the form action state.
44
+ * @private You don't need to use this component directly. Use the `createForm` function instead.
45
+ */
46
+ declare const FormContext: react.Context<FormMetaState<FormState<unknown, unknown, unknown>> | null>;
47
+ /**
48
+ * A hook to consume the form action state from the context.
49
+ */
50
+ declare const useFormContext: () => FormMetaState<FormState<unknown, unknown, unknown>>;
51
+
52
+ declare function FormStatus({ children, }: RenderProp<ReturnType<typeof useFormStatus>>): react.ReactNode;
25
53
 
26
- declare function FormStatus({ children, }: RenderProp<ReturnType<typeof useFormStatus>>): React.ReactNode;
54
+ /**
55
+ * Conditionally renders the children, when the form action is in the "pending" state.
56
+ */
57
+ declare function Pending({ children }: PropsWithChildren): react.ReactNode;
27
58
 
28
59
  declare const SEPARATOR: ".";
29
60
  type Paths<Err> = {
@@ -36,6 +67,6 @@ type ZodFieldErrorProps<Errors extends ZodFormattedError<any>> = {
36
67
  } & Partial<RenderProp<{
37
68
  errors: readonly string[];
38
69
  }>>;
39
- declare function ZodFieldError<Errors extends ZodFormattedError<any>>({ errors, name, children, }: ZodFieldErrorProps<Errors>): React__default.ReactNode;
70
+ declare function ZodFieldError<Errors extends ZodFormattedError<any>>({ errors, name, children, }: ZodFieldErrorProps<Errors>): react__default.ReactNode;
40
71
 
41
- export { Form, FormProps, FormStateProps, FormStatus, InferZodErrorPaths, Paths, ZodFieldError, ZodFieldErrorProps, initial };
72
+ export { Form, FormContext, FormMetaState, FormProps, FormStateProps, FormStatus, InferZodErrorPaths, Paths, Pending, ZodFieldError, ZodFieldErrorProps, createForm, initial, useFormContext };
package/dist/client.mjs CHANGED
@@ -1 +1 @@
1
- "use client";import p from"react";import{useActionState as E}from"react";import{useFormStatus as c}from"react-dom";function s({children:t}){return t(c())}function F(t){return{type:"initial",data:t,error:null,validationError:null}}var i={isInitial:!1,isInvalid:!1,isFailure:!1,isSuccess:!1};function T({children:t,action:a,initialData:n,permalink:e,...o}){let[r,m]=E(a,F(n),e),d=r.type==="initial"?{...r,...i,isInitial:!0}:r.type==="invalid"?{...r,...i,isInvalid:!0}:r.type==="failure"?{...r,...i,isFailure:!0}:{...r,...i,isSuccess:!0};return p.createElement("form",{action:m,...o},p.createElement(s,null,({pending:u})=>t({...d,isPending:u})))}import l from"react";var f=".",S=Object.freeze({_errors:Object.freeze([])});function g({errors:t,name:a="",children:n=({errors:e})=>l.createElement(l.Fragment,null,e[0])}){if(a.length===0)return n({errors:t._errors});let e=a.split(f),o=t;for(;e.length;){let r=e.shift();o=o[r]??S}return n({errors:o._errors})}export{T as Form,s as FormStatus,g as ZodFieldError,F as initial};
1
+ "use client";import F from"react";import x,{useTransition as P}from"react";import{useActionState as D}from"react";function k(r){return{type:"initial",data:r,error:null,validationError:null}}var s={isInitial:!1,isInvalid:!1,isFailure:!1,isSuccess:!1};function d({children:r,action:e,initialData:n,permalink:t,autoReset:o=!1,...m}){let[a,l,i]=D(e,k(n),t),[,E]=P(),S=a.type==="initial"?{...a,...s,isPending:i,isInitial:!0}:a.type==="invalid"?{...a,...s,isPending:i,isInvalid:!0}:a.type==="failure"?{...a,...s,isPending:i,isFailure:!0}:{...a,...s,isPending:i,isSuccess:!0};return x.createElement("form",{...o?{action:l}:{onSubmit:u=>{u.preventDefault();let y=u.currentTarget;E(()=>{l(new FormData(y))})}},...m},r(S))}import{createContext as h,use as T}from"react";var p=h(null),c=()=>{let r=T(p);if(!r)throw new Error("Form Context must be initialized before use.");return r};function z(r){return{Form:({children:n,...t})=>F.createElement(d,{action:r,...t},o=>F.createElement(p,{value:o},n))}}import{useFormStatus as g}from"react-dom";function W({children:r}){return r(g())}function $({children:r}){let{isPending:e}=c();return e&&r}import f from"react";var v=".",w=Object.freeze({_errors:Object.freeze([])});function G({errors:r,name:e="",children:n=({errors:t})=>f.createElement(f.Fragment,null,t[0])}){if(e.length===0)return n({errors:r._errors});let t=e.split(v),o=r;for(;t.length;){let m=t.shift();o=o[m]??w}return n({errors:o._errors})}export{d as Form,p as FormContext,W as FormStatus,$ as Pending,G as ZodFieldError,z as createForm,k as initial,c as useFormContext};
@@ -23,10 +23,11 @@ type SuccessState<T> = {
23
23
  validationError: null;
24
24
  };
25
25
  type FormState<Data, Error, ValidationError = Record<string, never>> = InitialState<Data> | InvalidState<ValidationError> | FailureState<Error> | SuccessState<Data>;
26
+ type FormAction<Data, Error = Data, ValidationError = Record<string, never>, Payload = FormData> = (state: FormState<Data, Error, ValidationError>, payload: Payload) => Promise<FormState<Data, Error, ValidationError>>;
26
27
  declare function createFormAction<Data, Error = Data, ValidationError = Record<string, never>, Payload = FormData>(formAction: (params: {
27
28
  success: (data: Data) => SuccessState<Data>;
28
29
  failure: (error: Error) => FailureState<Error>;
29
30
  invalid: (validationError: ValidationError) => InvalidState<ValidationError>;
30
- }) => (state: FormState<Data, Error, ValidationError>, payload: Payload) => Promise<FormState<Data, Error, ValidationError>>): (state: FormState<Data, Error, ValidationError>, payload: Payload) => Promise<FormState<Data, Error, ValidationError>>;
31
+ }) => FormAction<Data, Error, ValidationError, Payload>): (state: FormState<Data, Error, ValidationError>, payload: Payload) => Promise<FormState<Data, Error, ValidationError>>;
31
32
 
32
- export { FormState as F, InvalidState as I, SuccessState as S, FailureState as a, InitialState as b, createFormAction as c };
33
+ export { FormState as F, InvalidState as I, SuccessState as S, FailureState as a, InitialState as b, FormAction as c, createFormAction as d };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FormState, I as InvalidState, a as FailureState, S as SuccessState } from './createFormAction-3a51310f.js';
2
- export { b as InitialState, c as createFormAction } from './createFormAction-3a51310f.js';
1
+ import { F as FormState, I as InvalidState, a as FailureState, S as SuccessState } from './createFormAction-252fadef.js';
2
+ export { c as FormAction, b as InitialState, d as createFormAction } from './createFormAction-252fadef.js';
3
3
  import { ZodTypeAny, ZodType, ZodObject, ZodRawShape, objectUtil, z, ZodSchema, ZodEffects } from 'zod';
4
4
 
5
5
  declare const emptyInput: unique symbol;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- function S(e){function o(n){return{type:"success",data:n,error:null,validationError:null}}function a(n){return{type:"failure",data:null,error:n,validationError:null}}function s(n){return{type:"invalid",data:null,error:null,validationError:n}}return(n,f)=>e({success:o,failure:a,invalid:s})(n,f)}import{ZodFirstPartyTypeKind as C}from"zod";import{zfd as w}from"zod-form-data";var l=Symbol();function d(e,o=[],a){async function s(t){let r={formData:t};for(let i of o){let u=await i({ctx:r});r={...r,...u}}return r}return{input(t){if(e===l)return d(t,o,a);if(e._def.effect)throw new Error("Previous input is not augmentable because it contains an effect.");if(t._def.effect)throw new Error("Your input contains effect which prevents merging it with the previous inputs.");if(e._def.typeName===C.ZodObject){let r=e.merge(t);return d(r,o)}else throw Error("Merging inputs works only for object schemas without effects.")},use(t){return d(e,[...o,t],a)},error(t){return d(e,o,t)},run:e===l?t=>S(({success:r,failure:i})=>async(u,m)=>{let x=await s(m);try{return r(await t({ctx:x}))}catch(c){if(a)return i(a({error:c,ctx:x}));throw c}}):e===l?void 0:t=>S(({success:r,failure:i,invalid:u})=>{let m=w.formData(e);return async(x,c)=>{let y=await s(c),p=m.safeParse(c);if(!p.success)return u(p.error.format());let h=p.data;try{return r(await t({input:h,ctx:y}))}catch(E){if(a)return i(a({error:E,ctx:y}));throw E}}})}}var b=d(l);export{S as createFormAction,b as formAction};
1
+ function y(e){function o(n){return{type:"success",data:n,error:null,validationError:null}}function a(n){return{type:"failure",data:null,error:n,validationError:null}}function s(n){return{type:"invalid",data:null,error:null,validationError:n}}return(n,S)=>e({success:o,failure:a,invalid:s})(n,S)}import{ZodFirstPartyTypeKind as C}from"zod";import{zfd as w}from"zod-form-data";var l=Symbol();function d(e,o=[],a){async function s(t){let r={formData:t};for(let i of o){let u=await i({ctx:r});r={...r,...u}}return r}return{input(t){if(e===l)return d(t,o,a);if(e._def.effect)throw new Error("Previous input is not augmentable because it contains an effect.");if(t._def.effect)throw new Error("Your input contains effect which prevents merging it with the previous inputs.");if(e._def.typeName===C.ZodObject){let r=e.merge(t);return d(r,o)}else throw Error("Merging inputs works only for object schemas without effects.")},use(t){return d(e,[...o,t],a)},error(t){return d(e,o,t)},run:e===l?t=>y(({success:r,failure:i})=>async(u,m)=>{let x=await s(m);try{return r(await t({ctx:x}))}catch(c){if(a)return i(a({error:c,ctx:x}));throw c}}):e===l?void 0:t=>y(({success:r,failure:i,invalid:u})=>{let m=w.formData(e);return async(x,c)=>{let f=await s(c),p=m.safeParse(c);if(!p.success)return u(p.error.format());let h=p.data;try{return r(await t({input:h,ctx:f}))}catch(E){if(a)return i(a({error:E,ctx:f}));throw E}}})}}var P=d(l);export{y as createFormAction,P as formAction};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-form-action",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "State management helpers for the react form actions.",
5
5
  "repository": {
6
6
  "type": "git",