Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# v0.0.9(unreleased)


- Add `dirtyFields` field to `formState`
- Add `valueAsNumber` field to the option argument of `register`

# v0.0.8

Expand Down
15 changes: 13 additions & 2 deletions doc/transformation.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ type rec valuesOfInputs =
| Object(Js.Dict.t<valuesOfInputs>)
| Array(array<valuesOfInputs>)

type fieldDirtyOfInputs = {
example?: bool,
exampleRequired?: bool,
cart?: array<fieldDirtyOfItem>
}

type rec useFormReturnOfInputs<'setValueAs> = {
control: controlOfInputs,
register: (variantOfInputs, ~options: registerOptionsOfInputs<'setValueAs>=?) => JsxDOM.domProps,
Expand All @@ -52,8 +58,13 @@ type rec useFormReturnOfInputs<'setValueAs> = {
}
and controlOfInputs
and variantOfInputs = | @as("example") Example | @as("exampleRequired") ExampleRequired | @as("cart") Cart
and registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs: 'setValueAs}
and formStateOfInputs = {isDirty: bool, isValid: bool, errors: fieldErrorsOfInputs}
and registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs, valueAsNumber?: bool}
and formStateOfInputs = {
isDirty: bool,
isValid: bool,
errors: fieldErrorsOfInputs,
dirtyFields: fieldDirtyOfInputs
}
and fieldErrorsOfInputs = {
example?: fieldErrorOfInputs,
exampleRequired?: fieldErrorOfInputs,
Expand Down
71 changes: 65 additions & 6 deletions src/ppx/signature.ml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,52 @@ let map_type_decl
]
in
let type_decls3 =
Sig.type_ Recursive
[
(* type fieldDirtyOfInputs = { example: bool, exampleRequired: bool, cart?: array<fieldDirtyOfItem> } *)
Type.mk
(mkloc ("fieldDirtyOf" ^ capitalize record_name) ptype_loc)
~priv:Public
~kind:
(Ptype_record
(lds
|> List.map
(fun (({ pld_type } : label_declaration) as ld) ->
match pld_type with
(* type fieldDirtyOfInputs = {cart?: array<fieldDirtyOfItem>} *)
| {
ptyp_desc =
Ptyp_constr
( { txt = Lident "array" },
[
{
ptyp_desc =
Ptyp_constr ({ txt = Lident l }, []);
};
] );
} ->
{
ld with
pld_type =
Typ.constr (lid "array")
[
Typ.constr
(lid @@ "fieldDirtyOf" ^ capitalize l)
[];
];
pld_attributes =
add_optional_attribute ld.pld_attributes;
}
| _ ->
{
ld with
pld_type = Typ.constr (lid "bool") [];
pld_attributes =
add_optional_attribute ld.pld_attributes;
})));
]
in
let type_decls4 =
Sig.type_ Recursive
[
(* type useFormReturnOfInputs<'setValueAs> = {
Expand Down Expand Up @@ -332,7 +378,7 @@ let map_type_decl
(mkloc ("variantOf" ^ capitalize record_name) ptype_loc)
~priv:Public
~kind:(Ptype_variant (make_const_decls fields ptype_loc));
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs} *)
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs, valueAsNumber?: bool} *)
Type.mk
(mkloc ("registerOptionsOf" ^ capitalize record_name) ptype_loc)
~params:[ (Typ.var "setValueAs", (NoVariance, NoInjectivity)) ]
Expand All @@ -345,8 +391,16 @@ let map_type_decl
(Typ.constr (lid "bool") []);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "setValueAs") (Typ.var "setValueAs");
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "valueAsNumber")
(Typ.constr (lid "bool") []);
]);
(* type formStateOfInputs = {isDirty: bool, isValid: bool, errors: fieldErrorsOfInputs} *)
(* type formStateOfInputs = {
isDirty: bool,
isValid: bool,
errors: fieldErrorsOfInputs,
dirtyFields: fieldDirtyOfInputs
} *)
Type.mk
(mkloc ("formStateOf" ^ capitalize record_name) ptype_loc)
~priv:Public
Expand All @@ -361,6 +415,10 @@ let map_type_decl
(Typ.constr
(lid @@ "fieldErrorsOf" ^ capitalize record_name)
[]);
Type.field ~mut:Immutable (mknoloc "dirtyFields")
(Typ.constr
(lid @@ "fieldDirtyOf" ^ capitalize record_name)
[]);
]);
(* type fieldErrorsOfInputs = { example: fieldErrorOfInputs, exampleRequired: fieldErrorOfInputs } *)
Type.mk
Expand Down Expand Up @@ -606,7 +664,7 @@ let map_type_decl
disabled: bool=?,
exact: bool=?,
} *)
let type_decls4 =
let type_decls5 =
Sig.type_ Recursive
[
Type.mk
Expand Down Expand Up @@ -670,7 +728,7 @@ let map_type_decl
]))
in

let type_decls5 =
let type_decls6 =
lds
|> List.filter_map
(fun
Expand Down Expand Up @@ -868,12 +926,13 @@ let map_type_decl
type_decls1;
type_decls2;
type_decls3;
type_decls4;
primitive_use_form;
module_controller;
type_decls4;
type_decls5;
primitive_use_watch;
]
@ type_decls5 @ primitive_use_field_array @ vd_field_array
@ type_decls6 @ primitive_use_field_array @ vd_field_array
| _ -> fail ptype_loc "This type is not handled by @ppx_react_hook_form"
else []

Expand Down
71 changes: 65 additions & 6 deletions src/ppx/structure.ml
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,52 @@ let map_type_decl
]
in
let type_decls3 =
Str.type_ Recursive
[
(* type fieldDirtyOfInputs = { example: bool, exampleRequired: bool, cart?: array<fieldDirtyOfItem> } *)
Type.mk
(mkloc ("fieldDirtyOf" ^ capitalize record_name) ptype_loc)
~priv:Public
~kind:
(Ptype_record
(lds
|> List.map
(fun (({ pld_type } : label_declaration) as ld) ->
match pld_type with
(* type fieldDirtyOfInputs = {cart?: array<fieldDirtyOfItem>} *)
| {
ptyp_desc =
Ptyp_constr
( { txt = Lident "array" },
[
{
ptyp_desc =
Ptyp_constr ({ txt = Lident l }, []);
};
] );
} ->
{
ld with
pld_type =
Typ.constr (lid "array")
[
Typ.constr
(lid @@ "fieldDirtyOf" ^ capitalize l)
[];
];
pld_attributes =
add_optional_attribute ld.pld_attributes;
}
| _ ->
{
ld with
pld_type = Typ.constr (lid "bool") [];
pld_attributes =
add_optional_attribute ld.pld_attributes;
})));
]
in
let type_decls4 =
Str.type_ Recursive
[
(* type useFormReturnOfInputs<'setValueAs> = {
Expand Down Expand Up @@ -331,7 +377,7 @@ let map_type_decl
(mkloc ("variantOf" ^ capitalize record_name) ptype_loc)
~priv:Public
~kind:(Ptype_variant (make_const_decls fields ptype_loc));
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs} *)
(* type registerOptionsOfInputs<'setValueAs> = {required?: bool, setValueAs?: 'setValueAs, valueAsNumber?: bool} *)
Type.mk
(mkloc ("registerOptionsOf" ^ capitalize record_name) ptype_loc)
~params:[ (Typ.var "setValueAs", (NoVariance, NoInjectivity)) ]
Expand All @@ -344,8 +390,16 @@ let map_type_decl
(Typ.constr (lid "bool") []);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "setValueAs") (Typ.var "setValueAs");
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "valueAsNumber")
(Typ.constr (lid "bool") []);
]);
(* type formStateOfInputs = {isDirty: bool, isValid: bool, errors: fieldErrorsOfInputs} *)
(* type formStateOfInputs = {
isDirty: bool,
isValid: bool,
errors: fieldErrorsOfInputs,
dirtyFields: fieldDirtyOfInputs,
} *)
Type.mk
(mkloc ("formStateOf" ^ capitalize record_name) ptype_loc)
~priv:Public
Expand All @@ -360,6 +414,10 @@ let map_type_decl
(Typ.constr
(lid @@ "fieldErrorsOf" ^ capitalize record_name)
[]);
Type.field ~mut:Immutable (mknoloc "dirtyFields")
(Typ.constr
(lid @@ "fieldDirtyOf" ^ capitalize record_name)
[]);
]);
(* type fieldErrorsOfInputs = { example: fieldErrorOfInputs, exampleRequired: fieldErrorOfInputs } *)
Type.mk
Expand Down Expand Up @@ -605,7 +663,7 @@ let map_type_decl
disabled: bool=?,
exact: bool=?,
} *)
let type_decls4 =
let type_decls5 =
Str.type_ Recursive
[
Type.mk
Expand Down Expand Up @@ -669,7 +727,7 @@ let map_type_decl
]))
in

let type_decls5 =
let type_decls6 =
lds
|> List.filter_map
(fun
Expand Down Expand Up @@ -1056,12 +1114,13 @@ let map_type_decl
type_decls1;
type_decls2;
type_decls3;
type_decls4;
primitive_use_form;
module_controller;
type_decls4;
type_decls5;
primitive_use_watch;
]
@ type_decls5 @ primitive_use_field_array @ vb_field_array
@ type_decls6 @ primitive_use_field_array @ vb_field_array
| _ -> fail ptype_loc "This type is not handled by @ppx_react_hook_form"
else []

Expand Down
4 changes: 3 additions & 1 deletion test/src/pages/basic/Basic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ export default function Basic() {
register,
handleSubmit,
watch,
formState: { errors },
formState: { errors, dirtyFields },
} = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data)

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

console.log(dirtyFields) // { example: true, exampleRequired: true }

return (
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
<form onSubmit={handleSubmit(onSubmit)}>
Expand Down
2 changes: 2 additions & 0 deletions test/src/pages/basic_res/Basic.res
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ let default = () => {

let exampleFieldState = getFieldState(Example, formState)
Js.log(exampleFieldState)
let isExampleFieldDirty = formState.dirtyFields.exampleRequired
Js.log(isExampleFieldDirty)
Js.log(setValue)

<form onSubmit={handleSubmit(onSubmit)}>
Expand Down
17 changes: 13 additions & 4 deletions test/src/pages/field_array/FieldArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function App() {
register,
control,
handleSubmit,
formState: { errors }
formState: { errors, dirtyFields }
} = useForm<inputs>({
defaultValues: {
cart: [{ name: "test", quantity: 1, price: 23 }]
Expand All @@ -41,6 +41,12 @@ export default function App() {

console.log("!!", errors)

const isCartPriceFieldDirty = (index: number) =>
(dirtyFields.cart?.[index]?.price) ?
dirtyFields.cart?.[index]?.price
:
false

return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
Expand Down Expand Up @@ -73,10 +79,13 @@ export default function App() {
})}
className={errors?.cart?.[index]?.price ? "error" : ""}
/>
<button type="button" onClick={() => remove(index)}>
DELETE
</button>
<span>
{(isCartPriceFieldDirty(index) ? "dirty" : "clean")}
</span>
</section>
<button type="button" onClick={() => remove(index)}>
DELETE
</button>
</div>
);
})}
Expand Down
20 changes: 18 additions & 2 deletions test/src/pages/field_array_res/FieldArray.res
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type inputs = {cart: array<item>}

@react.component
let default = () => {
let {register, control, handleSubmit, formState: {errors}} = useFormOfInputs(
let {register, control, handleSubmit, formState: {errors, dirtyFields}} = useFormOfInputs(
~options={
defaultValues: {
cart: [{quantity: 1., price: 23.}],
Expand All @@ -20,6 +20,14 @@ let default = () => {
)
let {fields, append, remove} = useFieldArrayOfInputsCart({name: Cart, control})

let isCartPriceFieldDirty = (index: int) =>
switch dirtyFields.cart->Option.flatMap(Belt.Array.get(_, index)) {
| Some({price}) => price
| _ => false
}

Js.log(isCartPriceFieldDirty(0))

let onSubmit = (data: inputs) => Js.log(data)

<form onSubmit={handleSubmit(onSubmit)}>
Expand All @@ -40,7 +48,15 @@ let default = () => {
| Some({quantity: ?Some({message})}) => message->React.string
| _ => React.null
}}
<input {...register((Cart, index, Price)->fieldArrayOfCart)} placeholder="price" />
<input
{...register(
(Cart, index, Price)->fieldArrayOfCart,
~options={required: true, valueAsNumber: true},
)}
placeholder="price"
className={isCartPriceFieldDirty(index) ? "error" : ""}
/>
<span> {(isCartPriceFieldDirty(index) ? "dirty" : "clean")->React.string} </span>
{switch errors.cart->ReactHookForm.getFieldError(index) {
| Some({price: ?Some({message})}) => message->React.string
| _ => React.null
Expand Down