Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit d5edfe7

Browse files
committed
more generalized props type for ref
1 parent 152b8a9 commit d5edfe7

11 files changed

+221
-35
lines changed

cli/reactjs_jsx_v4.ml

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ let recordFromProps ~loc ~removeKey callArguments =
233233
let makePropsTypeParamsTvar namedTypeList =
234234
namedTypeList
235235
|> List.filter_map (fun (_isOptional, label, _, _interiorType) ->
236-
if label = "key" || label = "ref" then None
236+
if label = "key" then None
237237
else Some (Typ.var @@ safeTypeFromValue (Labelled label)))
238238

239239
let stripOption coreType =
@@ -242,13 +242,36 @@ let stripOption coreType =
242242
List.nth_opt coreTypes 0 [@doesNotRaise]
243243
| _ -> Some coreType
244244

245-
(* make type params for make sig arguments and for external *)
246-
(* let make: React.componentLike<props<string, option<string>>, React.element> *)
247-
(* external make: React.componentLike<props< .. >, React.element> = "default" *)
248-
let makePropsTypeParams ?(stripExplicitOption = false) namedTypeList =
245+
let stripJsNullable coreType =
246+
match coreType with
247+
| {
248+
ptyp_desc =
249+
Ptyp_constr ({txt = Ldot (Ldot (Lident "Js", "Nullable"), "t")}, coreTypes);
250+
} ->
251+
List.nth_opt coreTypes 0
252+
| _ -> Some coreType
253+
254+
(* Make type params of the props type *)
255+
(* (Sig) let make: React.componentLike<props<string>, React.element> *)
256+
(* (Str) let make = ({x, _}: props<'x>) => body *)
257+
(* (Str) external make: React.componentLike<props< .. >, React.element> = "default" *)
258+
let makePropsTypeParams ?(stripExplicitOption = false)
259+
?(stripExplicitJsNullableOfRef = false) namedTypeList =
249260
namedTypeList
250261
|> List.filter_map (fun (isOptional, label, _, interiorType) ->
251-
if label = "key" || label = "ref" then None
262+
if label = "key" then None
263+
(* TODO: Worth thinking how about "ref_" or "_ref" usages *)
264+
else if label = "ref" then
265+
(*
266+
If ref has a type annotation then use it, else `ReactDOM.Ref.currentDomRef.
267+
For example, if JSX ppx is used for React Native, type would be different.
268+
*)
269+
match interiorType with
270+
| {ptyp_desc = Ptyp_var "ref"} -> Some (refType Location.none)
271+
| _ ->
272+
(* Strip explicit Js.Nullable.t in case of forwardRef *)
273+
if stripExplicitJsNullableOfRef then stripJsNullable interiorType
274+
else Some interiorType
252275
(* Strip the explicit option type in implementation *)
253276
(* let make = (~x: option<string>=?) => ... *)
254277
else if isOptional && stripExplicitOption then stripOption interiorType
@@ -259,10 +282,6 @@ let makeLabelDecls ~loc namedTypeList =
259282
|> List.map (fun (isOptional, label, _, interiorType) ->
260283
if label = "key" then
261284
Type.field ~loc ~attrs:optionalAttr {txt = label; loc} interiorType
262-
else if label = "ref" then
263-
Type.field ~loc
264-
~attrs:(if isOptional then optionalAttr else [])
265-
{txt = label; loc} interiorType
266285
else if isOptional then
267286
Type.field ~loc ~attrs:optionalAttr {txt = label; loc}
268287
(Typ.var @@ safeTypeFromValue @@ Labelled label)
@@ -614,9 +633,22 @@ let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes coreType
614633
| Pexp_fun
615634
( Nolabel,
616635
_,
617-
{ppat_desc = Ppat_var _ | Ppat_constraint ({ppat_desc = Ppat_var _}, _)},
636+
({
637+
ppat_desc =
638+
Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _);
639+
} as pattern),
618640
_expression ) ->
619-
(args, newtypes, coreType)
641+
if txt = "ref" then
642+
let type_ =
643+
match pattern with
644+
| {ppat_desc = Ppat_constraint (_, type_)} -> Some type_
645+
| _ -> None
646+
in
647+
(* The ref arguement of forwardRef should be optional *)
648+
( (Optional "ref", None, pattern, txt, pattern.ppat_loc, type_) :: args,
649+
newtypes,
650+
coreType )
651+
else (args, newtypes, coreType)
620652
| Pexp_fun (Nolabel, _, pattern, _expression) ->
621653
Location.raise_errorf ~loc:pattern.ppat_loc
622654
"React: react.component refs only support plain arguments and type \
@@ -943,10 +975,7 @@ let transformStructureItem ~config mapper item =
943975
let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in
944976
(* type props = { ... } *)
945977
let propsRecordType =
946-
makePropsRecordType "props" emptyLoc
947-
((if hasForwardRef then [(true, "ref", [], refType Location.none)]
948-
else [])
949-
@ namedTypeList)
978+
makePropsRecordType "props" emptyLoc namedTypeList
950979
in
951980
let innerExpression =
952981
Exp.apply
@@ -1013,12 +1042,12 @@ let transformStructureItem ~config mapper item =
10131042
| Pexp_fun
10141043
(arg_label, _default, ({ppat_loc; ppat_desc} as pattern), expr)
10151044
-> (
1016-
let pattern = stripConstraint pattern in
1045+
let patternWithoutConstraint = stripConstraint pattern in
10171046
if isLabelled arg_label || isOptional arg_label then
10181047
returnedExpression
10191048
(( {loc = ppat_loc; txt = Lident (getLabel arg_label)},
10201049
{
1021-
pattern with
1050+
patternWithoutConstraint with
10221051
ppat_attributes =
10231052
(if isOptional arg_label then optionalAttr else [])
10241053
@ pattern.ppat_attributes;
@@ -1029,7 +1058,8 @@ let transformStructureItem ~config mapper item =
10291058
(* Special case of nolabel arg "ref" in forwardRef fn *)
10301059
(* let make = React.forwardRef(ref => body) *)
10311060
match ppat_desc with
1032-
| Ppat_var {txt} ->
1061+
| Ppat_var {txt}
1062+
| Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) ->
10331063
returnedExpression patternsWithLabel
10341064
(( {loc = ppat_loc; txt = Lident txt},
10351065
{
@@ -1046,27 +1076,29 @@ let transformStructureItem ~config mapper item =
10461076
let patternsWithLabel, patternsWithNolabel, expression =
10471077
returnedExpression [] [] expression
10481078
in
1049-
let pattern =
1050-
match patternsWithLabel with
1051-
| [] -> Pat.any ()
1052-
| _ -> Pat.record (List.rev patternsWithLabel) Open
1053-
in
10541079
(* add pattern matching for optional prop value *)
10551080
let expression =
10561081
if List.length vbMatchList = 0 then expression
10571082
else Exp.let_ Nonrecursive vbMatchList expression
10581083
in
1084+
(* (ref) => expr *)
10591085
let expression =
10601086
List.fold_left
10611087
(fun expr (_, pattern) -> Exp.fun_ Nolabel None pattern expr)
10621088
expression patternsWithNolabel
10631089
in
1090+
let recordPattern =
1091+
match patternsWithLabel with
1092+
| [] -> Pat.any ()
1093+
| _ -> Pat.record (List.rev patternsWithLabel) Open
1094+
in
10641095
let expression =
10651096
Exp.fun_ Nolabel None
1066-
(Pat.constraint_ pattern
1097+
(Pat.constraint_ recordPattern
10671098
(Typ.constr ~loc:emptyLoc
10681099
{txt = Lident "props"; loc = emptyLoc}
1069-
(makePropsTypeParams ~stripExplicitOption:true namedTypeList)))
1100+
(makePropsTypeParams ~stripExplicitOption:true
1101+
~stripExplicitJsNullableOfRef:hasForwardRef namedTypeList)))
10701102
expression
10711103
in
10721104
(* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
@@jsxConfig({version: 3})
2+
3+
module V3 = {
4+
@obj
5+
external makeProps: (
6+
~x: string,
7+
~ref: ReactDOM.Ref.currentDomRef=?,
8+
~key: string=?,
9+
unit,
10+
) => {"x": string, "ref": option<ReactDOM.Ref.currentDomRef>} = ""
11+
@module("componentForwardRef")
12+
external make: React.componentLike<
13+
{"x": string, "ref": option<ReactDOM.Ref.currentDomRef>},
14+
React.element,
15+
> = "component"
16+
}
17+
18+
@@jsxConfig({version: 4, mode: "classic"})
19+
20+
module V4C = {
21+
type props<'x, 'ref> = {x: 'x, ref?: 'ref}
22+
23+
@module("componentForwardRef")
24+
external make: React.componentLike<props<string, ReactDOM.Ref.currentDomRef>, React.element> =
25+
"component"
26+
}
27+
28+
@@jsxConfig({version: 4, mode: "automatic"})
29+
30+
module V4C = {
31+
type props<'x, 'ref> = {x: 'x, ref?: 'ref}
32+
33+
@module("componentForwardRef")
34+
external make: React.componentLike<props<string, ReactDOM.Ref.currentDomRef>, React.element> =
35+
"component"
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@@jsxConfig({version: 3})
2+
3+
module V3 = {
4+
@obj
5+
external makeProps: (
6+
~x: t<'a>,
7+
~children: React.element,
8+
~key: string=?,
9+
unit,
10+
) => {"x": t<'a>, "children": React.element} = ""
11+
@module("c")
12+
external make: React.componentLike<{"x": t<'a>, "children": React.element}, React.element> =
13+
"component"
14+
}
15+
16+
@@jsxConfig({version: 4, mode: "classic"})
17+
18+
module V4C = {
19+
type props<'x, 'children> = {x: 'x, children: 'children}
20+
21+
@module("c")
22+
external make: React.componentLike<props<t<'a>, React.element>, React.element> = "component"
23+
}
24+
25+
@@jsxConfig({version: 4, mode: "automatic"})
26+
27+
module V4C = {
28+
type props<'x, 'children> = {x: 'x, children: 'children}
29+
30+
@module("c")
31+
external make: React.componentLike<props<t<'a>, React.element>, React.element> = "component"
32+
}

tests/ppx/react/expected/forwardRef.res.txt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,17 @@ module V3 = {
6666

6767
module V4C = {
6868
module FancyInput = {
69-
type props<'className, 'children> = {
70-
ref?: ReactDOM.Ref.currentDomRef,
69+
type props<'className, 'children, 'ref> = {
7170
className?: 'className,
7271
children: 'children,
72+
ref?: 'ref,
7373
}
7474

7575
@react.component
76-
let make = ({?className, children, _}: props<'className, 'children>, ref) =>
76+
let make = (
77+
{?className, children, _}: props<'className, 'children, ReactRef.currentDomRef>,
78+
ref: Js.Nullable.t<ReactRef.currentDomRef>,
79+
) =>
7780
ReactDOMRe.createDOMElementVariadic(
7881
"div",
7982
[
@@ -82,7 +85,7 @@ module V4C = {
8285
~props=ReactDOMRe.domProps(
8386
~type_="text",
8487
~className?,
85-
~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)},
88+
~ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(React.Ref.domRef)},
8689
(),
8790
),
8891
[],
@@ -123,14 +126,17 @@ module V4C = {
123126

124127
module V4A = {
125128
module FancyInput = {
126-
type props<'className, 'children> = {
127-
ref?: ReactDOM.Ref.currentDomRef,
129+
type props<'className, 'children, 'ref> = {
128130
className?: 'className,
129131
children: 'children,
132+
ref?: 'ref,
130133
}
131134

132135
@react.component
133-
let make = ({?className, children, _}: props<'className, 'children>, ref) =>
136+
let make = (
137+
{?className, children, _}: props<'className, 'children, ReactDOM.Ref.currentDomRef>,
138+
ref,
139+
) =>
134140
ReactDOM.jsxs(
135141
"div",
136142
{
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
type props<'x, 'ref> = {x: 'x, ref?: 'ref}
2+
@react.component
3+
let make = (
4+
{x, _}: props<string, ReactDOM.Ref.currentDomRef>,
5+
ref: Js.Nullable.t<ReactDOM.Ref.currentDomRef>,
6+
) => {
7+
let _ = ref->Js.Nullable.toOption->Belt.Option.map(ReactDOM.Ref.domRef)
8+
React.string(x)
9+
}
10+
let make = React.forwardRef({
11+
let \"InterfaceWithRef" = (props: props<_>, ref) => make(props, ref)
12+
\"InterfaceWithRef"
13+
})
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
type props<'x, 'ref> = {x: 'x, ref?: 'ref}
2+
let make: React.componentLike<props<string, ReactDOM.Ref.currentDomRef>, React.element>

tests/ppx/react/externalWithRef.res

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@@jsxConfig({version: 3})
2+
3+
module V3 = {
4+
@module("componentForwardRef") @react.component
5+
external make: (
6+
~x: string,
7+
~ref: ReactDOM.Ref.currentDomRef=?,
8+
) => React.element = "component"
9+
}
10+
11+
@@jsxConfig({version: 4, mode: "classic"})
12+
13+
module V4C = {
14+
@module("componentForwardRef") @react.component
15+
external make: (
16+
~x: string,
17+
~ref: ReactDOM.Ref.currentDomRef=?,
18+
) => React.element = "component"
19+
}
20+
21+
@@jsxConfig({version: 4, mode: "automatic"})
22+
23+
module V4C = {
24+
@module("componentForwardRef") @react.component
25+
external make: (
26+
~x: string,
27+
~ref: ReactDOM.Ref.currentDomRef=?,
28+
) => React.element = "component"
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
@@jsxConfig({version: 3})
2+
3+
module V3 = {
4+
@module("c") @react.component
5+
external make: (
6+
~x: t<'a>,
7+
~children: React.element,
8+
) => React.element = "component"
9+
}
10+
11+
@@jsxConfig({version: 4, mode: "classic"})
12+
13+
module V4C = {
14+
@module("c") @react.component
15+
external make: (
16+
~x: t<'a>,
17+
~children: React.element,
18+
) => React.element = "component"
19+
}
20+
21+
@@jsxConfig({version: 4, mode: "automatic"})
22+
23+
module V4C = {
24+
@module("c") @react.component
25+
external make: (
26+
~x: t<'a>,
27+
~children: React.element,
28+
) => React.element = "component"
29+
}

tests/ppx/react/forwardRef.res

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ module V3 = {
3030
module V4C = {
3131
module FancyInput = {
3232
@react.component
33-
let make = React.forwardRef((~className=?, ~children, ref) =>
33+
let make = React.forwardRef((~className=?, ~children, ref: Js.Nullable.t<ReactRef.currentDomRef>) =>
3434
<div>
3535
<input
3636
type_="text"
3737
?className
38-
ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(ReactDOM.Ref.domRef)}
38+
ref=?{Js.Nullable.toOption(ref)->Belt.Option.map(React.Ref.domRef)}
3939
/>
4040
children
4141
</div>

tests/ppx/react/interfaceWithRef.res

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@react.component
2+
let make = React.forwardRef((~x: string, ref: Js.Nullable.t<ReactDOM.Ref.currentDomRef>) => {
3+
let _ = ref->Js.Nullable.toOption->Belt.Option.map(ReactDOM.Ref.domRef)
4+
React.string(x)
5+
})

tests/ppx/react/interfaceWithRef.resi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@react.component
2+
let make: (~x: string, ~ref: ReactDOM.Ref.currentDomRef=?) => React.element

0 commit comments

Comments
 (0)