Creating Bindings for React Transition Group React.JS components

interop
reasonreact

#1

I am attempting to use ReactJS components react-transition-group for creating bindings for it - http://reactcommunity.org/react-transition-group/transition.

How to do I model the children attribute where it either accepts either an React.element or a function which returns a React.component -http://reactcommunity.org/react-transition-group/transition#Transition-prop-children

I tried the following approach but bucklescript complains with the following error message
bs.obj label children does not support [@bs.unwrap] arguments

module Transition = {
  [@bs.module "react-transition-group/Transition"] [@react.component]
  external make:
    (
      ~_in: bool,
      ~timeout: int,
      ~children: [@bs.unwrap][ | `Func(string => React.element) | `Elem(React.element)]
    ) =>
    React.element =
    "default";
};

Alternately, I imported the Transition component via the following mechanism. However, I feel the approach above would be much better if I can make it work. Any pointers?

module TransitionElem = {
  [@bs.module "react-transition-group/Transition"] [@react.component]
  external make:
    (
      ~_in: bool,
      ~timeout: int,
      ~children: React.element
    ) =>
    React.element =
    "default";
};

module TransitionFunc = {
  [@bs.module "react-transition-group/Transition"] [@react.component]
  external make:
    (~_in: bool, ~timeout: int, ~children: string => React.element) =>
    React.element =
    "default";
};

#2

As always with functions that has different argument types, you would make 2 bindings for it.


#3

As always with functions that has different argument types, you would make 2 bindings for it.

Oh! Is there a documentation which explains this? Alternately, I just tried gentype but am getting error again.

module Transition4 = {
  [@genType.import "react-transition-group/Transition"]
  external make:
    (~_in: bool, ~timeout: int, 'a) =>
    ReasonReact.component(
      ReasonReact.stateless,
      ReasonReact.noRetainedProps,
      ReasonReact.actionless,
    ) =
    "make";

  let make = make;
};

and using it like so,

<Transition4 _in=inState timeout=500> <p> "Hello" </p> </Transition4>

The error:

44 │       {state => <div> {React.string({j|I'm a in $state Transition!|j})
       } </div>}
  45 │     </TransitionF>
  46 │     <Transition4 _in=inState timeout=500> <p> "Hello" </p> </Transitio
       n4>
  47 │   </div>;
  48 │ };
  
  This has type:
    (~_in: bool, ~timeout: int, 'a) =>
    ReasonReact.component(ReasonReact.stateless, ReasonReact.noRetainedProps,
                           ReasonReact.actionless)
  But somewhere wanted:
    React.component('b) (defined as 'b => React.element)
  
>>>> Finish compiling(exit: 1)

Any pointers again?


#4

Aha! the below seems to work, i.e. making the ~children param a generic.

module Transition5 = {
  [@react.component] [@bs.module "react-transition-group/Transition"]
  external make: (~_in: bool, ~timeout: int, ~children: 'a) => React.element =
    "default";
};

You can use it like so - Note both function and React.element children are accepted.

<Transition5 _in=inState timeout=500>
      <p> {React.string("Hello Transition5")} </p>
</Transition5>
<Transition5 _in=inState timeout=500>
      {state =>
         <div> {React.string({j|Transition5: I'm a in $state!|j})} </div>
      }
</Transition5>

#5

You lose type safety then no?


#6

You lose type safety then no?

Indeed. Not sure if there is a way to define one Transition component in ReasonReact that accepts both types of children. @bobzhang @cristianoc any pointers perhaps?


#7

@bikallem Another approach (that preserves type safety within the Reason code) would be to use a combination of:

  • make / makeProps (instead of [@react.component])
  • an %identity external
  • an abstract type for the children on the JS side

It could look something like this:

module Transition = {
  type func = string => React.element;
  type elem = React.element;
  type children = [ | `Func(func) | `Elem(elem)];

  /* Abstract type representing the children on the JS side */
  type jsChildren;
  
  /* Conversion functions to the abstract jsChildren type */
  external funcToJsChildren: func => jsChildren = "%identity";
  external elemToJsChildren: elem => jsChildren = "%identity";
  type jsProps = {
    .
    "in": bool,
    "timeout": int,
    "children": jsChildren,
  };

  let makeProps = (~_in: bool, ~timeout: int, ~children: children, ()) => {
    "in": _in,
    "timeout": timeout,
    "children":
      switch (children) {
      | `Func(func) => funcToJsChildren(func)
      | `Elem(elem) => elemToJsChildren(elem)
      },
  };

  [@bs.module "react-transition-group/Transition"]
  external make: React.component(jsProps) = "default";
};

Check out more information about these features here:


#8

Very nice. Thanks. This works like a charm.:+1::+1: