Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix forwardRef unsafety + make DOM refs types more idiomatic #19

Merged
merged 1 commit into from
Jun 2, 2019
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
35 changes: 19 additions & 16 deletions bin/Refs.re
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
let map = (f, opt) =>
switch (opt) {
| Some(s) => Some(f(s))
| None => None
};
let log = a => Js_of_ocaml.Firebug.console##log(a);

module FancyButton = {
module FancyLink = {
[@react.component]
let make =
React.forwardRef(ref => {
let console = Js_of_ocaml.Firebug.console;
console##log("FancyButton got ref");
console##log(ref);
<button ref=?{map(ReactDOM.Ref.domRef, ref)} className="FancyButton">
{"Click me!" |> React.string}
</button>;
});
ReactDOM.forwardRef(ref =>
<a ?ref href="https://github.com/jchavarri/rroo/" className="FancyLink">
{"rroo GitHub repo" |> React.string}
</a>
);
};

[@react.component]
let make = () => {
let (show, setShow) = React.useState(() => true);
// You can now get a ref directly to the DOM button:
let ref = React.createRef();
<FancyButton ref />;
let ref =
ReactDOM.Ref.callbackDomRef(ref => {
log(Js_of_ocaml.Js.string("Ref is:"));
log(ref);
});
<>
<button onClick={_ => setShow(s => !s)}>
{"Toggle fancy link" |> React.string}
</button>
{show ? <FancyLink ref /> : React.null}
</>;
};
5 changes: 0 additions & 5 deletions lib/React.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,6 @@ void createRef () {
exit(1);
}

void forwardRef () {
fprintf(stderr, "Unimplemented React function `forwardRef`!\n");
exit(1);
}

void useRef () {
fprintf(stderr, "Unimplemented React function `useRef`!\n");
exit(1);
Expand Down
20 changes: 6 additions & 14 deletions lib/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,19 @@ var useEffect = useEffectFunction(React.useEffect);
var useLayoutEffect = useEffectFunction(React.useLayoutEffect);

// Provides: Ref_current
// Requires: React
var Ref_current = React.current;
var Ref_current = function(ref, value) {
return ref.current;
};

// Provides: Ref_setCurrent
// Requires: React
var Ref_setCurrent = React.setCurrent;
var Ref_setCurrent = function(ref, value) {
ref.current = value;
};

// Provides: createRef
// Requires: React
var createRef = React.createRef;

// Provides: forwardRef
// Requires: React, caml_js_wrap_callback, jsNullToOption
var forwardRef = function(callback) {
var jsCallback = caml_js_wrap_callback(callback);
return React.forwardRef(function(props, jsRef) {
var ref = jsNullToOption(jsRef);
return jsCallback(props, jsRef);
});
};

// Provides: useRef
// Requires: React
var useRef = React.useRef;
4 changes: 0 additions & 4 deletions lib/React.re
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ module Ref = {

external createRef: unit => Ref.t(option('a)) = "createRef";

external forwardRef:
(('props, option(Ref.t('a))) => element) => component('props) =
"forwardRef";

// module Children = {
// external map: (element, element => element) => element = "Children_map";
// external forEach: (element, element => unit) => unit = "Children_forEach";
Expand Down
15 changes: 15 additions & 0 deletions lib/ReactDOM.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ void render () {
exit(1);
}

void domRef () {
fprintf(stderr, "Unimplemented ReactDOM function `domRef`!\n");
exit(1);
}

void callbackDomRef () {
fprintf(stderr, "Unimplemented ReactDOM function `callbackDomRef`!\n");
exit(1);
}

void forwardRef () {
fprintf(stderr, "Unimplemented ReactDOM function `forwardRef`!\n");
exit(1);
}

void domProps () {
fprintf(stderr, "Unimplemented ReactDOM function `domProps`!\n");
exit(1);
Expand Down
31 changes: 30 additions & 1 deletion lib/ReactDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,44 @@ var ReactDOM = joo_global_object.ReactDOM;
// Requires: ReactDOM
var render = ReactDOM.render;

// Provides: domRef
// Requires: jsNullToOption
var domRef = function(ref) {
var jsCallback = function(element) {
ref.current = jsNullToOption(element);
};
return jsCallback;
};

// Provides: callbackDomRef
// Requires: jsNullToOption
var callbackDomRef = function(cb) {
var jsCallback = function(element) {
cb(jsNullToOption(element));
};
return jsCallback;
};

// Provides: forwardRef
// Requires: React, caml_js_wrap_callback
var forwardRef = function(callback) {
var jsCallback = caml_js_wrap_callback(callback);
return React.forwardRef(function(props, ref) {
// No need to convert refs, as they'll be processed in callbackDomRef and domRef functions
return jsCallback(props, ref);
});
};

// Provides: domProps
// Requires: setIfSome, setIfSomeMap, caml_js_from_string, caml_js_wrap_callback
var domProps = function(key, ref, alt, async, className, onClick) {
var domProps = function(key, ref, alt, async, className, href, onClick) {
var tmp = {};
setIfSomeMap(tmp, "key", key, caml_js_from_string);
setIfSome(tmp, "ref", ref);
setIfSomeMap(tmp, "alt", alt, caml_js_from_string);
setIfSome(tmp, "async", async);
setIfSomeMap(tmp, "className", className, caml_js_from_string);
setIfSomeMap(tmp, "href", href, caml_js_from_string);
setIfSomeMap(tmp, "onClick", onClick, caml_js_wrap_callback);
return tmp;
};
Expand Down
16 changes: 12 additions & 4 deletions lib/ReactDOM.re
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,29 @@ let optInj = (~f=?, prop, opt) =>
type domRef;
module Ref = {
type t = domRef;
type currentDomRef = React.Ref.t(Js.opt(Dom_html.element));
type callbackDomRef = Js.opt(Dom_html.element) => unit;
type currentDomRef = React.Ref.t(option(Dom_html.element));
type callbackDomRef = option(Dom_html.element) => unit;

external domRef: currentDomRef => domRef = "%identity";
external callbackDomRef: callbackDomRef => domRef = "%identity";
external domRef: currentDomRef => domRef = "domRef";
external callbackDomRef: callbackDomRef => domRef = "callbackDomRef";
};

external forwardRef:
(('props, option(domRef)) => React.element) => React.component('props) =
"forwardRef";

type domProps;

/* Important: the order of these labelled arguments must match the order in which
the params are listed in the ReactDOM.js external implementation of `domProps` */
external domProps:
(
~key: string=?,
~ref: domRef=?,
~alt: string=?,
~async: bool=?,
~className: string=?,
~href: string=?,
~onClick: ReactEvent.Mouse.t => unit=?,
unit
) =>
Expand Down
5 changes: 4 additions & 1 deletion ppx/Rroo_jsoo_ppx.re
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ let keyType = loc =>
[Typ.constr(~loc, {loc, txt: Lident("string")}, [])],
);

let refType = loc =>
Typ.constr(~loc, {loc, txt: Ldot(Lident("ReactDOM"), "domRef")}, []);

type children('a) =
| ListLiteral('a)
| Exact('a);
Expand Down Expand Up @@ -1110,7 +1113,7 @@ let jsxMapper = () => {
Pat.var({txt: "key", loc: emptyLoc}),
"ref",
emptyLoc,
None,
Some(refType(emptyLoc)),
),
...namedArgListWithKeyAndRef,
]
Expand Down
18 changes: 18 additions & 0 deletions ppx/test/pp.expected
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,21 @@ let make =
^ name))
()))|] in
Test
module ForwardRef =
struct
let makeProps
: ?key:string -> ?ref:ReactDOM.domRef -> unit -> < > Js_of_ocaml.Js.t
=
fun ?key ->
fun ?ref ->
fun _ ->
let open Js_of_ocaml.Js.Unsafe in
obj [|("ref", (inject ref));("key", (inject key))|]
let make =
React.forwardRef
(let Test$ForwardRef (Props : < > Js_of_ocaml.Js.t) theRef =
ReactDOM.createDOMElementVariadic "div"
~props:(ReactDOM.domProps ~ref:theRef ())
[|("ForwardRef" |. React.string)|] in
Test$ForwardRef)
end
8 changes: 8 additions & 0 deletions ppx/test/test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ let make ?(name= "") =
())
[@JSX ])])
[@JSX ])[@@react.component ]
module ForwardRef =
struct
let make =
React.forwardRef
(fun theRef ->
((div ~ref:theRef ~children:["ForwardRef" |. React.string] ())
[@JSX ]))[@@react.component ]
end
8 changes: 8 additions & 0 deletions ppx/test/test_src.re
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ let make = (~name="") => {
<Hello one="1"> {React.string("Hello " ++ name)} </Hello>
</>;
};

module ForwardRef = {
[@react.component]
let make =
React.forwardRef(theRef =>
<div ref=theRef> "ForwardRef"->React.string </div>
);
};