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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# v0.0.8 (unrelease)

- Add `useWatch` hook

# v0.0.7

- Add `getValues` to `useForm` hook
Expand Down
11 changes: 11 additions & 0 deletions doc/transformation.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ module ControllerOfInputs = {
) => React.element = "Controller"
}

//useWatch
type useWatchParamsOfInputs = {
name: variantOfInputs,
control: controlOfInputs=?,
defaultValue: valueOfInputs=?,
disabled: bool=?,
exact: bool=?,
}
@module("react-hook-form")
external useWatchOfInputs: useWatchParamsOfInputs => option<valueOfInputs>

// useFieldArray
type rec useFieldArrayReturnOfInputsCart = {
fields: array<itemWithId>,
Expand Down
75 changes: 74 additions & 1 deletion src/ppx/signature.ml
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,78 @@ let map_type_decl
]))
in

(* type useWatchParamsOfInputs = {
name: variantOfInputs,
control: controlOfInputs=?,
defaultValue: valueOfInputs=?,
disabled: bool=?,
exact: bool=?,
} *)
let type_decls4 =
Sig.type_ Recursive
[
Type.mk
(mkloc ("useWatchParamsOf" ^ capitalize record_name) ptype_loc)
~priv:Public
~kind:
(Ptype_record
[
Type.field ~mut:Immutable (mknoloc "name")
(Typ.constr
(lid @@ "variantOf" ^ capitalize record_name)
[]);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "control")
(Typ.constr ~attrs:[ attr_named_arg ]
(lid @@ "controlOf" ^ capitalize record_name)
[]);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "defaultValue")
(Typ.constr
(lid @@ "valuesOf" ^ capitalize record_name)
[]);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "disabled")
(Typ.constr (lid "bool") []);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "exact")
(Typ.constr (lid "bool") []);
]);
]
in

(* @module("react-hook-form")
external useWatchOfInputs: useWatchParamsOfInputs => option<valuesOfInputs> = "useWatch" *)
let primitive_use_watch =
Sig.value
(Val.mk
~attrs:
[
Attr.mk (mknoloc "module")
(PStr
[
Str.eval
@@ Exp.constant (Const.string "react-hook-form");
]);
]
~prim:[ "useWatch" ]
(mknoloc @@ "useWatchOf" ^ capitalize record_name)
(uncurried_core_type_arrow ~arity:1
[
Typ.arrow Nolabel
(Typ.constr ~attrs:[ attr_named_arg ]
(lid @@ "useWatchParamsOf" ^ capitalize record_name)
[])
(Typ.constr (lid "option")
[
Typ.constr
(lid @@ "valuesOf" ^ capitalize record_name)
[];
]);
]))
in

let type_decls5 =
lds
|> List.filter_map
(fun
Expand Down Expand Up @@ -799,8 +870,10 @@ let map_type_decl
type_decls3;
primitive_use_form;
module_controller;
type_decls4;
primitive_use_watch;
]
@ type_decls4 @ primitive_use_field_array @ vd_field_array
@ type_decls5 @ primitive_use_field_array @ vd_field_array
| _ -> fail ptype_loc "This type is not handled by @ppx_react_hook_form"
else []

Expand Down
75 changes: 74 additions & 1 deletion src/ppx/structure.ml
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,78 @@ let map_type_decl
]))
in

(* type useWatchParamsOfInputs = {
name: variantOfInputs,
control: controlOfInputs=?,
defaultValue: valuesOfInputs=?,
disabled: bool=?,
exact: bool=?,
} *)
let type_decls4 =
Str.type_ Recursive
[
Type.mk
(mkloc ("useWatchParamsOf" ^ capitalize record_name) ptype_loc)
~priv:Public
~kind:
(Ptype_record
[
Type.field ~mut:Immutable (mknoloc "name")
(Typ.constr
(lid @@ "variantOf" ^ capitalize record_name)
[]);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "control")
(Typ.constr ~attrs:[ attr_named_arg ]
(lid @@ "controlOf" ^ capitalize record_name)
[]);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "defaultValue")
(Typ.constr
(lid @@ "valuesOf" ^ capitalize record_name)
[]);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "disabled")
(Typ.constr (lid "bool") []);
Type.field ~attrs:[ attr_optional ] ~mut:Immutable
(mknoloc "exact")
(Typ.constr (lid "bool") []);
]);
]
in

(* @module("react-hook-form")
external useWatchOfInputs: useWatchParamsOfInputs => option<valuesOfInputs> = "useWatch" *)
let primitive_use_watch =
Str.primitive
(Val.mk
~attrs:
[
Attr.mk (mknoloc "module")
(PStr
[
Str.eval
@@ Exp.constant (Const.string "react-hook-form");
]);
]
~prim:[ "useWatch" ]
(mknoloc @@ "useWatchOf" ^ capitalize record_name)
(uncurried_core_type_arrow ~arity:1
[
Typ.arrow Nolabel
(Typ.constr ~attrs:[ attr_named_arg ]
(lid @@ "useWatchParamsOf" ^ capitalize record_name)
[])
(Typ.constr (lid "option")
[
Typ.constr
(lid @@ "valuesOf" ^ capitalize record_name)
[];
]);
]))
in

let type_decls5 =
lds
|> List.filter_map
(fun
Expand Down Expand Up @@ -987,8 +1058,10 @@ let map_type_decl
type_decls3;
primitive_use_form;
module_controller;
type_decls4;
primitive_use_watch;
]
@ type_decls4 @ primitive_use_field_array @ vb_field_array
@ type_decls5 @ primitive_use_field_array @ vb_field_array
| _ -> fail ptype_loc "This type is not handled by @ppx_react_hook_form"
else []

Expand Down
35 changes: 35 additions & 0 deletions test/src/pages/use_watch/UseWatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useForm, SubmitHandler, useWatch } from "react-hook-form"

type Inputs = {
example: string
exampleRequired: string
}

export default function UseWatch() {
const {
register,
control,
handleSubmit,
formState: { errors },
} = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = (data) => console.log(data)

const value = useWatch({ name: "example", control, defaultValue: "test" })

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

return (
/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input defaultValue="test" {...register("example")} />

{/* include validation with required or other standard HTML validation rules */}
<input {...register("exampleRequired", { required: true })} />
{/* errors will return when field validation fails */}
{errors.exampleRequired && <span>This field is required</span>}

<input type="submit" />
</form>
)
}
24 changes: 24 additions & 0 deletions test/src/pages/use_watch_res/UseWatch.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@rhf
type inputs = {
example?: string,
exampleRequired: string,
}

@react.component @genType
let default = () => {
let {register, control, handleSubmit, formState} = useFormOfInputs()
let onSubmit = (data: inputs) => Js.log(data)

let example = useWatchOfInputs({name: Example, control, defaultValue: String("test")})

Js.log(example)

<form onSubmit={handleSubmit(onSubmit)}>
<input {...register(Example)} defaultValue="test" />
<input {...register(ExampleRequired, ~options={required: true})} />
{formState.errors.exampleRequired->Belt.Option.isSome
? <span> {"This field is required"->React.string} </span>
: React.null}
<input type_="submit" />
</form>
}
11 changes: 10 additions & 1 deletion test/src/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import Controller from "../pages/controller/Controller"
import ControllerRes from "../pages/controller_res/Controller.gen"
import FieldArray from "../pages/field_array/FieldArray";
import FieldArrayRes from "../pages/field_array_res/FieldArray.gen";

import UseWatch from "../pages/use_watch/UseWatch";
import UseWatchRes from "../pages/use_watch_res/UseWatch.gen";
const router = createBrowserRouter([
{
path: "/",
Expand Down Expand Up @@ -48,6 +49,14 @@ const router = createBrowserRouter([
path: "/field_array_res",
element: <FieldArrayRes />
},
{
path: "/use_watch",
element: <UseWatch />
},
{
path: "/use_watch_res",
element: <UseWatchRes />
},
])

export default router