Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 261
user465374 Avatar asked Mar 08 '23 16:03

user465374


2 Answers

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));
like image 58
InsidersByte Avatar answered Mar 10 '23 06:03

InsidersByte


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.

like image 31
glennsl Avatar answered Mar 10 '23 04:03

glennsl