Reputation: 1541
When defining a reason-react binding and I want to know how I can determine a binding that accepts multiple types. For example, I have an argument ~value
that should accept: string
, number
, array(string)
or array(number)
. At the moment I am using option('a)
but I do not think this is the cleanest approach as I would prefer to define the type explicitly. How can this be done? I have looked at bs.unwrap
but I am unsure how to combine external syntax into a function signature.
module Select = {
[@bs.module "material-ui/Select"] external reactClass : ReasonReact.reactClass = "default";
let make =
(
...
~menuProps: option(Js.t({..}))=?,
~value: option('a), /* Should be type to string, number, Array of string and Array of number */
~style: option(ReactDOMRe.style)=?,
...
children
) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=
Js.Nullable.(
{
...
"value": from_opt(value),
"style": from_opt(style)
}
),
children
);
};
As a side question, as number type is not defined in reason would my binding also have to map float and integer into numbers?
Upvotes: 4
Views: 640
Reputation: 96
This is possible by using the following (inspired by https://github.com/astrada/reason-react-toolbox/).
type jsUnsafe;
external toJsUnsafe : 'a => jsUnsafe = "%identity";
let unwrapValue =
(r: [< | `Int(int) | `IntArray(array(int)) | `String(string) | `StringArray(array(string))]) =>
switch r {
| `String(s) => toJsUnsafe(s)
| `Int(i) => toJsUnsafe(i)
| `StringArray(a) => toJsUnsafe(a)
| `IntArray(a) => toJsUnsafe(a)
};
let optionMap = (fn, option) =>
switch option {
| Some(value) => Some(fn(value))
| None => None
};
module Select = {
[@bs.module "material-ui/Select"] external reactClass : ReasonReact.reactClass = "default";
let make =
(
...
~menuProps: option(Js.t({..}))=?,
~value:
option(
[ | `Int(int) | `IntArray(array(int)) | `String(string) | `StringArray(array(string))]
)=?,
~style: option(ReactDOMRe.style)=?,
...
children
) =>
ReasonReact.wrapJsForReason(
~reactClass,
~props=
Js.Nullable.(
{
...
"value": from_opt(optionMap(unwrapValue, value)),
"style": from_opt(style)
}
),
children
);
};
This can be used in the following way;
<Select value=(`IntArray([|10, 20|])) />
<Select value=(`Int(10)) />
I copied toJsUnsafe
from reason-react-toolbox, so I'm not entirely sure exactly what it does, I will update my answer when I find out.
The unwrapValue
function takes a value which can be one of the types listed and converts it to jsUnsafe.
The type for unwrapValue
allows for any of variants listed, but also allows a subset of those, for example. (It's the <
before the variants that enable this).
let option = (value: option([ | `String(string) | `Int(int)])) =>
Js.Nullable.from_opt(option_map(unwrapValue, value));
Upvotes: 4
Reputation: 29106
Just to add to @InsidersByte's answer, since this problem isn't reason-react-specific and can be generalized:
module Value = {
type t;
external int : int => t = "%identity";
external intArray : array(int) => t = "%identity";
external string : string => t = "%identity";
external stringArray : array(string) => t = "%identity";
};
let values : list(Value.t) = [
Value.int(4),
Value.stringArray([|"foo", "bar"|])
];
This solution is also self-contained inside the Value
module, and incurs no overhead compared to the JavaScript equivalent since "%identity"
externals are no-ops that are optimized away.
Upvotes: 3