Skip to content

Commit 80370eb

Browse files
authored
Add dirtyFields field to formState (#16)
* Add dirtyFields to formState * change log * change log
1 parent 33ed3cf commit 80370eb

File tree

8 files changed

+181
-22
lines changed

8 files changed

+181
-22
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# v0.0.9(unreleased)
22

3-
3+
- Add `dirtyFields` field to `formState`
4+
- Add `valueAsNumber` field to the option argument of `register`
45

56
# v0.0.8
67

doc/transformation.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ type rec valuesOfInputs =
3939
| Object(Js.Dict.t<valuesOfInputs>)
4040
| Array(array<valuesOfInputs>)
4141
42+
type fieldDirtyOfInputs = {
43+
example?: bool,
44+
exampleRequired?: bool,
45+
cart?: array<fieldDirtyOfItem>
46+
}
47+
4248
type rec useFormReturnOfInputs<'setValueAs> = {
4349
control: controlOfInputs,
4450
register: (variantOfInputs, ~options: registerOptionsOfInputs<'setValueAs>=?) => JsxDOM.domProps,
@@ -52,8 +58,13 @@ type rec useFormReturnOfInputs<'setValueAs> = {
5258
}
5359
and controlOfInputs
5460
and variantOfInputs = | @as("example") Example | @as("exampleRequired") ExampleRequired | @as("cart") Cart
55-
and registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs: 'setValueAs}
56-
and formStateOfInputs = {isDirty: bool, isValid: bool, errors: fieldErrorsOfInputs}
61+
and registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs, valueAsNumber?: bool}
62+
and formStateOfInputs = {
63+
isDirty: bool,
64+
isValid: bool,
65+
errors: fieldErrorsOfInputs,
66+
dirtyFields: fieldDirtyOfInputs
67+
}
5768
and fieldErrorsOfInputs = {
5869
example?: fieldErrorOfInputs,
5970
exampleRequired?: fieldErrorOfInputs,

src/ppx/signature.ml

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,52 @@ let map_type_decl
169169
]
170170
in
171171
let type_decls3 =
172+
Sig.type_ Recursive
173+
[
174+
(* type fieldDirtyOfInputs = { example: bool, exampleRequired: bool, cart?: array<fieldDirtyOfItem> } *)
175+
Type.mk
176+
(mkloc ("fieldDirtyOf" ^ capitalize record_name) ptype_loc)
177+
~priv:Public
178+
~kind:
179+
(Ptype_record
180+
(lds
181+
|> List.map
182+
(fun (({ pld_type } : label_declaration) as ld) ->
183+
match pld_type with
184+
(* type fieldDirtyOfInputs = {cart?: array<fieldDirtyOfItem>} *)
185+
| {
186+
ptyp_desc =
187+
Ptyp_constr
188+
( { txt = Lident "array" },
189+
[
190+
{
191+
ptyp_desc =
192+
Ptyp_constr ({ txt = Lident l }, []);
193+
};
194+
] );
195+
} ->
196+
{
197+
ld with
198+
pld_type =
199+
Typ.constr (lid "array")
200+
[
201+
Typ.constr
202+
(lid @@ "fieldDirtyOf" ^ capitalize l)
203+
[];
204+
];
205+
pld_attributes =
206+
add_optional_attribute ld.pld_attributes;
207+
}
208+
| _ ->
209+
{
210+
ld with
211+
pld_type = Typ.constr (lid "bool") [];
212+
pld_attributes =
213+
add_optional_attribute ld.pld_attributes;
214+
})));
215+
]
216+
in
217+
let type_decls4 =
172218
Sig.type_ Recursive
173219
[
174220
(* type useFormReturnOfInputs<'setValueAs> = {
@@ -332,7 +378,7 @@ let map_type_decl
332378
(mkloc ("variantOf" ^ capitalize record_name) ptype_loc)
333379
~priv:Public
334380
~kind:(Ptype_variant (make_const_decls fields ptype_loc));
335-
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs} *)
381+
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs, valueAsNumber?: bool} *)
336382
Type.mk
337383
(mkloc ("registerOptionsOf" ^ capitalize record_name) ptype_loc)
338384
~params:[ (Typ.var "setValueAs", (NoVariance, NoInjectivity)) ]
@@ -345,8 +391,16 @@ let map_type_decl
345391
(Typ.constr (lid "bool") []);
346392
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
347393
(mknoloc "setValueAs") (Typ.var "setValueAs");
394+
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
395+
(mknoloc "valueAsNumber")
396+
(Typ.constr (lid "bool") []);
348397
]);
349-
(* type formStateOfInputs = {isDirty: bool, isValid: bool, errors: fieldErrorsOfInputs} *)
398+
(* type formStateOfInputs = {
399+
isDirty: bool,
400+
isValid: bool,
401+
errors: fieldErrorsOfInputs,
402+
dirtyFields: fieldDirtyOfInputs
403+
} *)
350404
Type.mk
351405
(mkloc ("formStateOf" ^ capitalize record_name) ptype_loc)
352406
~priv:Public
@@ -361,6 +415,10 @@ let map_type_decl
361415
(Typ.constr
362416
(lid @@ "fieldErrorsOf" ^ capitalize record_name)
363417
[]);
418+
Type.field ~mut:Immutable (mknoloc "dirtyFields")
419+
(Typ.constr
420+
(lid @@ "fieldDirtyOf" ^ capitalize record_name)
421+
[]);
364422
]);
365423
(* type fieldErrorsOfInputs = { example: fieldErrorOfInputs, exampleRequired: fieldErrorOfInputs } *)
366424
Type.mk
@@ -606,7 +664,7 @@ let map_type_decl
606664
disabled: bool=?,
607665
exact: bool=?,
608666
} *)
609-
let type_decls4 =
667+
let type_decls5 =
610668
Sig.type_ Recursive
611669
[
612670
Type.mk
@@ -670,7 +728,7 @@ let map_type_decl
670728
]))
671729
in
672730

673-
let type_decls5 =
731+
let type_decls6 =
674732
lds
675733
|> List.filter_map
676734
(fun
@@ -868,12 +926,13 @@ let map_type_decl
868926
type_decls1;
869927
type_decls2;
870928
type_decls3;
929+
type_decls4;
871930
primitive_use_form;
872931
module_controller;
873-
type_decls4;
932+
type_decls5;
874933
primitive_use_watch;
875934
]
876-
@ type_decls5 @ primitive_use_field_array @ vd_field_array
935+
@ type_decls6 @ primitive_use_field_array @ vd_field_array
877936
| _ -> fail ptype_loc "This type is not handled by @ppx_react_hook_form"
878937
else []
879938

src/ppx/structure.ml

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,52 @@ let map_type_decl
168168
]
169169
in
170170
let type_decls3 =
171+
Str.type_ Recursive
172+
[
173+
(* type fieldDirtyOfInputs = { example: bool, exampleRequired: bool, cart?: array<fieldDirtyOfItem> } *)
174+
Type.mk
175+
(mkloc ("fieldDirtyOf" ^ capitalize record_name) ptype_loc)
176+
~priv:Public
177+
~kind:
178+
(Ptype_record
179+
(lds
180+
|> List.map
181+
(fun (({ pld_type } : label_declaration) as ld) ->
182+
match pld_type with
183+
(* type fieldDirtyOfInputs = {cart?: array<fieldDirtyOfItem>} *)
184+
| {
185+
ptyp_desc =
186+
Ptyp_constr
187+
( { txt = Lident "array" },
188+
[
189+
{
190+
ptyp_desc =
191+
Ptyp_constr ({ txt = Lident l }, []);
192+
};
193+
] );
194+
} ->
195+
{
196+
ld with
197+
pld_type =
198+
Typ.constr (lid "array")
199+
[
200+
Typ.constr
201+
(lid @@ "fieldDirtyOf" ^ capitalize l)
202+
[];
203+
];
204+
pld_attributes =
205+
add_optional_attribute ld.pld_attributes;
206+
}
207+
| _ ->
208+
{
209+
ld with
210+
pld_type = Typ.constr (lid "bool") [];
211+
pld_attributes =
212+
add_optional_attribute ld.pld_attributes;
213+
})));
214+
]
215+
in
216+
let type_decls4 =
171217
Str.type_ Recursive
172218
[
173219
(* type useFormReturnOfInputs<'setValueAs> = {
@@ -331,7 +377,7 @@ let map_type_decl
331377
(mkloc ("variantOf" ^ capitalize record_name) ptype_loc)
332378
~priv:Public
333379
~kind:(Ptype_variant (make_const_decls fields ptype_loc));
334-
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs} *)
380+
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs, valueAsNumber?: bool} *)
335381
Type.mk
336382
(mkloc ("registerOptionsOf" ^ capitalize record_name) ptype_loc)
337383
~params:[ (Typ.var "setValueAs", (NoVariance, NoInjectivity)) ]
@@ -344,8 +390,16 @@ let map_type_decl
344390
(Typ.constr (lid "bool") []);
345391
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
346392
(mknoloc "setValueAs") (Typ.var "setValueAs");
393+
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
394+
(mknoloc "valueAsNumber")
395+
(Typ.constr (lid "bool") []);
347396
]);
348-
(* type formStateOfInputs = {isDirty: bool, isValid: bool, errors: fieldErrorsOfInputs} *)
397+
(* type formStateOfInputs = {
398+
isDirty: bool,
399+
isValid: bool,
400+
errors: fieldErrorsOfInputs,
401+
dirtyFields: fieldDirtyOfInputs,
402+
} *)
349403
Type.mk
350404
(mkloc ("formStateOf" ^ capitalize record_name) ptype_loc)
351405
~priv:Public
@@ -360,6 +414,10 @@ let map_type_decl
360414
(Typ.constr
361415
(lid @@ "fieldErrorsOf" ^ capitalize record_name)
362416
[]);
417+
Type.field ~mut:Immutable (mknoloc "dirtyFields")
418+
(Typ.constr
419+
(lid @@ "fieldDirtyOf" ^ capitalize record_name)
420+
[]);
363421
]);
364422
(* type fieldErrorsOfInputs = { example: fieldErrorOfInputs, exampleRequired: fieldErrorOfInputs } *)
365423
Type.mk
@@ -605,7 +663,7 @@ let map_type_decl
605663
disabled: bool=?,
606664
exact: bool=?,
607665
} *)
608-
let type_decls4 =
666+
let type_decls5 =
609667
Str.type_ Recursive
610668
[
611669
Type.mk
@@ -669,7 +727,7 @@ let map_type_decl
669727
]))
670728
in
671729

672-
let type_decls5 =
730+
let type_decls6 =
673731
lds
674732
|> List.filter_map
675733
(fun
@@ -1056,12 +1114,13 @@ let map_type_decl
10561114
type_decls1;
10571115
type_decls2;
10581116
type_decls3;
1117+
type_decls4;
10591118
primitive_use_form;
10601119
module_controller;
1061-
type_decls4;
1120+
type_decls5;
10621121
primitive_use_watch;
10631122
]
1064-
@ type_decls5 @ primitive_use_field_array @ vb_field_array
1123+
@ type_decls6 @ primitive_use_field_array @ vb_field_array
10651124
| _ -> fail ptype_loc "This type is not handled by @ppx_react_hook_form"
10661125
else []
10671126

test/src/pages/basic/Basic.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ export default function Basic() {
1010
register,
1111
handleSubmit,
1212
watch,
13-
formState: { errors },
13+
formState: { errors, dirtyFields },
1414
} = useForm<Inputs>()
1515
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data)
1616

1717
console.log(watch("example")) // watch input value by passing the name of it
1818

19+
console.log(dirtyFields) // { example: true, exampleRequired: true }
20+
1921
return (
2022
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
2123
<form onSubmit={handleSubmit(onSubmit)}>

test/src/pages/basic_res/Basic.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ let default = () => {
3939

4040
let exampleFieldState = getFieldState(Example, formState)
4141
Js.log(exampleFieldState)
42+
let isExampleFieldDirty = formState.dirtyFields.exampleRequired
43+
Js.log(isExampleFieldDirty)
4244
Js.log(setValue)
4345

4446
<form onSubmit={handleSubmit(onSubmit)}>

test/src/pages/field_array/FieldArray.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function App() {
2626
register,
2727
control,
2828
handleSubmit,
29-
formState: { errors }
29+
formState: { errors, dirtyFields }
3030
} = useForm<inputs>({
3131
defaultValues: {
3232
cart: [{ name: "test", quantity: 1, price: 23 }]
@@ -41,6 +41,12 @@ export default function App() {
4141

4242
console.log("!!", errors)
4343

44+
const isCartPriceFieldDirty = (index: number) =>
45+
(dirtyFields.cart?.[index]?.price) ?
46+
dirtyFields.cart?.[index]?.price
47+
:
48+
false
49+
4450
return (
4551
<div>
4652
<form onSubmit={handleSubmit(onSubmit)}>
@@ -73,10 +79,13 @@ export default function App() {
7379
})}
7480
className={errors?.cart?.[index]?.price ? "error" : ""}
7581
/>
76-
<button type="button" onClick={() => remove(index)}>
77-
DELETE
78-
</button>
82+
<span>
83+
{(isCartPriceFieldDirty(index) ? "dirty" : "clean")}
84+
</span>
7985
</section>
86+
<button type="button" onClick={() => remove(index)}>
87+
DELETE
88+
</button>
8089
</div>
8190
);
8291
})}

test/src/pages/field_array_res/FieldArray.res

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type inputs = {cart: array<item>}
1010

1111
@react.component
1212
let default = () => {
13-
let {register, control, handleSubmit, formState: {errors}} = useFormOfInputs(
13+
let {register, control, handleSubmit, formState: {errors, dirtyFields}} = useFormOfInputs(
1414
~options={
1515
defaultValues: {
1616
cart: [{quantity: 1., price: 23.}],
@@ -20,6 +20,14 @@ let default = () => {
2020
)
2121
let {fields, append, remove} = useFieldArrayOfInputsCart({name: Cart, control})
2222

23+
let isCartPriceFieldDirty = (index: int) =>
24+
switch dirtyFields.cart->Option.flatMap(Belt.Array.get(_, index)) {
25+
| Some({price}) => price
26+
| _ => false
27+
}
28+
29+
Js.log(isCartPriceFieldDirty(0))
30+
2331
let onSubmit = (data: inputs) => Js.log(data)
2432

2533
<form onSubmit={handleSubmit(onSubmit)}>
@@ -40,7 +48,15 @@ let default = () => {
4048
| Some({quantity: ?Some({message})}) => message->React.string
4149
| _ => React.null
4250
}}
43-
<input {...register((Cart, index, Price)->fieldArrayOfCart)} placeholder="price" />
51+
<input
52+
{...register(
53+
(Cart, index, Price)->fieldArrayOfCart,
54+
~options={required: true, valueAsNumber: true},
55+
)}
56+
placeholder="price"
57+
className={isCartPriceFieldDirty(index) ? "error" : ""}
58+
/>
59+
<span> {(isCartPriceFieldDirty(index) ? "dirty" : "clean")->React.string} </span>
4460
{switch errors.cart->ReactHookForm.getFieldError(index) {
4561
| Some({price: ?Some({message})}) => message->React.string
4662
| _ => React.null

0 commit comments

Comments
 (0)