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

Commit bf6c721

Browse files
committed
more general props type for ref
1 parent b911f84 commit bf6c721

11 files changed

+221
-38
lines changed

cli/reactjs_jsx_ppx.ml

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,7 +1557,7 @@ module V4 = struct
15571557
let makePropsTypeParamsTvar namedTypeList =
15581558
namedTypeList
15591559
|> List.filter_map (fun (_isOptional, label, _, _interiorType) ->
1560-
if label = "key" || label = "ref" then None
1560+
if label = "key" then None
15611561
else Some (Typ.var @@ safeTypeFromValue (Labelled label)))
15621562

15631563
let stripOption coreType =
@@ -1566,13 +1566,37 @@ module V4 = struct
15661566
List.nth_opt coreTypes 0
15671567
| _ -> Some coreType
15681568

1569-
(* make type params for make sig arguments and for external *)
1570-
(* let make: React.componentLike<props<string, option<string>>, React.element> *)
1571-
(* external make: React.componentLike<props< .. >, React.element> = "default" *)
1572-
let makePropsTypeParams ?(stripExplicitOption = false) namedTypeList =
1569+
let stripJsNullable coreType =
1570+
match coreType with
1571+
| {
1572+
ptyp_desc =
1573+
Ptyp_constr
1574+
({txt = Ldot (Ldot (Lident "Js", "Nullable"), "t")}, coreTypes);
1575+
} ->
1576+
List.nth_opt coreTypes 0
1577+
| _ -> Some coreType
1578+
1579+
(* Make type params of the props type *)
1580+
(* (Sig) let make: React.componentLike<props<string>, React.element> *)
1581+
(* (Str) let make = ({x, _}: props<'x>) => body *)
1582+
(* (Str) external make: React.componentLike<props< .. >, React.element> = "default" *)
1583+
let makePropsTypeParams ?(stripExplicitOption = false)
1584+
?(stripExplicitJsNullableOfRef = false) namedTypeList =
15731585
namedTypeList
15741586
|> List.filter_map (fun (isOptional, label, _, interiorType) ->
1575-
if label = "key" || label = "ref" then None
1587+
if label = "key" then None
1588+
(* TODO: Worth thinking how about "ref_" or "_ref" usages *)
1589+
else if label = "ref" then
1590+
(*
1591+
If ref has a type annotation then use it, else `ReactDOM.Ref.currentDomRef.
1592+
For example, if JSX ppx is used for React Native, type would be different.
1593+
*)
1594+
match interiorType with
1595+
| {ptyp_desc = Ptyp_var label} -> Some (refType Location.none)
1596+
| _ ->
1597+
(* Strip explicit Js.Nullable.t in case of forwardRef *)
1598+
if stripExplicitJsNullableOfRef then stripJsNullable interiorType
1599+
else Some interiorType
15761600
(* Strip the explicit option type in implementation *)
15771601
(* let make = (~x: option<string>=?) => ... *)
15781602
else if isOptional && stripExplicitOption then
@@ -1584,10 +1608,6 @@ module V4 = struct
15841608
|> List.map (fun (isOptional, label, _, interiorType) ->
15851609
if label = "key" then
15861610
Type.field ~loc ~attrs:optionalAttr {txt = label; loc} interiorType
1587-
else if label = "ref" then
1588-
Type.field ~loc
1589-
~attrs:(if isOptional then optionalAttr else [])
1590-
{txt = label; loc} interiorType
15911611
else if isOptional then
15921612
Type.field ~loc ~attrs:optionalAttr {txt = label; loc}
15931613
(Typ.var @@ safeTypeFromValue @@ Labelled label)
@@ -1951,12 +1971,22 @@ module V4 = struct
19511971
| Pexp_fun
19521972
( Nolabel,
19531973
_,
1954-
{
1955-
ppat_desc =
1956-
Ppat_var _ | Ppat_constraint ({ppat_desc = Ppat_var _}, _);
1957-
},
1974+
({
1975+
ppat_desc =
1976+
Ppat_var {txt} | Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _);
1977+
} as pattern),
19581978
_expression ) ->
1959-
(args, newtypes, coreType)
1979+
if txt = "ref" then
1980+
let type_ =
1981+
match pattern with
1982+
| {ppat_desc = Ppat_constraint (_, type_)} -> Some type_
1983+
| _ -> None
1984+
in
1985+
(* The ref arguement of forwardRef should be optional *)
1986+
( (Optional "ref", None, pattern, txt, pattern.ppat_loc, type_) :: args,
1987+
newtypes,
1988+
coreType )
1989+
else (args, newtypes, coreType)
19601990
| Pexp_fun (Nolabel, _, pattern, _expression) ->
19611991
Location.raise_errorf ~loc:pattern.ppat_loc
19621992
"React: react.component refs only support plain arguments and type \
@@ -2293,11 +2323,7 @@ module V4 = struct
22932323
let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in
22942324
(* type props = { ... } *)
22952325
let propsRecordType =
2296-
makePropsRecordType "props" emptyLoc
2297-
((if hasForwardRef then
2298-
[(true, "ref", [], refType Location.none)]
2299-
else [])
2300-
@ namedTypeList)
2326+
makePropsRecordType "props" emptyLoc namedTypeList
23012327
in
23022328
let innerExpression =
23032329
Exp.apply
@@ -2366,12 +2392,12 @@ module V4 = struct
23662392
| Pexp_fun
23672393
(arg_label, _default, ({ppat_loc; ppat_desc} as pattern), expr)
23682394
-> (
2369-
let pattern = stripConstraint pattern in
2395+
let patternWithoutConstraint = stripConstraint pattern in
23702396
if isLabelled arg_label || isOptional arg_label then
23712397
returnedExpression
23722398
(( {loc = ppat_loc; txt = Lident (getLabel arg_label)},
23732399
{
2374-
pattern with
2400+
patternWithoutConstraint with
23752401
ppat_attributes =
23762402
(if isOptional arg_label then optionalAttr else [])
23772403
@ pattern.ppat_attributes;
@@ -2382,7 +2408,8 @@ module V4 = struct
23822408
(* Special case of nolabel arg "ref" in forwardRef fn *)
23832409
(* let make = React.forwardRef(ref => body) *)
23842410
match ppat_desc with
2385-
| Ppat_var {txt} ->
2411+
| Ppat_var {txt}
2412+
| Ppat_constraint ({ppat_desc = Ppat_var {txt}}, _) ->
23862413
returnedExpression patternsWithLabel
23872414
(( {loc = ppat_loc; txt = Lident txt},
23882415
{
@@ -2400,27 +2427,29 @@ module V4 = struct
24002427
let patternsWithLabel, patternsWithNolabel, expression =
24012428
returnedExpression [] [] expression
24022429
in
2403-
let pattern =
2404-
match patternsWithLabel with
2405-
| [] -> Pat.any ()
2406-
| _ -> Pat.record (List.rev patternsWithLabel) Open
2407-
in
24082430
(* add pattern matching for optional prop value *)
24092431
let expression =
24102432
if List.length vbMatchList = 0 then expression
24112433
else Exp.let_ Nonrecursive vbMatchList expression
24122434
in
24132435
let expression =
2436+
(* (ref) => expr *)
24142437
List.fold_left
24152438
(fun expr (_, pattern) -> Exp.fun_ Nolabel None pattern expr)
24162439
expression patternsWithNolabel
24172440
in
2441+
let recordPattern =
2442+
match patternsWithLabel with
2443+
| [] -> Pat.any ()
2444+
| _ -> Pat.record (List.rev patternsWithLabel) Open
2445+
in
24182446
let expression =
24192447
Exp.fun_ Nolabel None
2420-
(Pat.constraint_ pattern
2448+
(Pat.constraint_ recordPattern
24212449
(Typ.constr ~loc:emptyLoc
24222450
{txt = Lident "props"; loc = emptyLoc}
24232451
(makePropsTypeParams ~stripExplicitOption:true
2452+
~stripExplicitJsNullableOfRef:hasForwardRef
24242453
namedTypeList)))
24252454
expression
24262455
in
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)