ReasonML Variant Interop With Javascript

mxdavis
Astrolabe Diagnostics

--

At Astrolabe we are a small team committed to high quality and bug-free code. We are slowly transferring our Flow React components to Reason React to utilize Reason’s strong type system to detect bugs before they hit production.

I was tasked with switching over one component from Flow JS to ReasonReact, and I ran into an interesting problem. A prop being passed into the new ReasonReact component was suddenly coming through as an array, with the first element having the data I needed, and the second element tag: 1 [myProp, tag: 1] .

Usually how I look when programming in ReasonML

Another one of those what is ReasonReact doing moments.

Thanks to Rizo for clarifying this for me. This array was my props wrapped in a ReasonML variant. The first index was my element, and the second was showing which index it was in the list of variants.

For example:

type animal = 
| Cat(string)
| Dog(string);
let toAnimal =
fun
|Cat(name) => name
|Dog(name) => name;
let fluffyCat = Cat(“Fluffy”)
Js.log(fluffyCat)
/* ["Fluffy", tag: 0] */
let fluffy = toAnimal(fluffyCat)
Js.log(fluffy)
/* "Fluffy" */

fluffyCat is still locked inside the variant, and is not the string I was expecting to use, which was only available after I extracted it with the toAnimal switch.

When passing a variant from ReasonML to Javascript make sure to extract the value from the variant before sending it to Javascript. In other words, do not expect Javascript to handle fluffyCat (which will come in as an array) but rather fluffy which will have the expected string.

From Javascript to ReasonML: If Javascript sends a string to ReasonML, and ReasonML was expecting typeanimal this will result in undefined when trying to extract option(string)from a string instead of a Cat(string). It also would not be a good idea to have ReasonML expect a string to make Javascript happy since we want to expect an animal type to utilize Reason’s strong type system. So how can we pass in the variant from JS to ReasonML?

There’s gotta be a way do to this!

When passing in a variant from Javascript to ReasonReact JSX:2, we pass in props (“fluffy”) and we will call the switch from the props in ReasonReactWrapForJS . The Javascript component does not need to worry about the extraction, it will just send in the string as-is and ReasonML will handle the conversion.

let make = (~animal: animal, children) =>
ReasonReact.wrapJsForReason(
~reactClass=componentName,
~props={
"animal": toAnimal(animal)
},
children,
);

animal: toAnimal(animal) is key here, it takes the string we received through animal props, and converts it to a variant with our toAnimalswitch that is expecting a string. This conversion happens before it hits the ReasonReact JSX: 2 component which is expecting animal to be of type animal.

When passing in a variant from Javascript to ReasonReact JSX:3 we import things directly without this wrapper. This has made development so much simpler, and cleaner, but now we need to somehow make this variant type available inside our Javascript component. Thanks to Yawar λmin for pointing me to bucklescript deriving accessors. By adding [@bs.deriving abstract]above type animal it will export all our types in the bs.js file so we can import them into Javascript.

[@bs.deriving abstract]
type animal =
| Cat(string)
| Dog(string);
/*
In the bs.js file we now have:
exports.cat = cat;
exports.dog = dog;
*/

Now in the Javascript component we import { cat } from "fileName.bs.js" and wrap the string inside the imported variant. cat("Fluffy") will send ReasonML a variant type instead of just the string. Just note that the types in ReasonML are capitalized, and in the export to Javascript not.

Now we can use our variants from ReasonReact to Javascript and vice versa with JSX: 2 or JSX: 3 and continue to rely on ReasonML’s type system so we know if it compiles, it works.

To see the ReasonML and generated JS click here.

Happy Programming!

--

--