user465374
user465374

Reputation: 1541

How to define a binding that accepts multiple types in the function signature using reason-react?

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

Answers (2)

InsidersByte
InsidersByte

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

glennsl
glennsl

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

Related Questions