ReasonReact component export guidance

Does the following sound right?

  • name the component function “default” and don’t use reactcomponent when creating a React component that will only be used by javascript modules
  • name the component function “make” and use reactcomponent when creating a React component that will be used by rescript modules
  • name the component function “default” and use reactcomponent when creating a React component that will be used by both rescript and javascript modules
1 Like

Don’t use the ReasonReact modules for components, since they expose an obsolete “record based” component API.

Interop between ReScript & JS works like this: Whenever you are creating a ReScript / React component like this:

// MyComp.res
@react.component
let make = (~name: string) => {
  <div> {React.string(name)} </div>
}

It will be compiled to a JS equivalent of

// MyComp.js
var React = require("react");

function MyComp(Props) {
  var name = Props.name;
  return React.createElement("div", undefined, name);
}

var make = MyComp;

exports.make = make;

Which means you can use your component in other JS code like this:

import { make as MyComp } from "./MyComp.js"

But I guess you want a cleaner looking import such as import MyComp from "./MyComp.js", which can be achieved by aliasing the original make function to default:

// MyComp.res
@react.component
let make = (~name: string) => {
  <div> {React.string(name)} </div>
}

let default = make;

This way, you can use MyComp both in ReScript JSX, and also import it nicely in JS.

Just make sure that your props only define data types that are interoperable between ReScript & JS, otherwise you will need genType to create auto converters for you.

7 Likes

Is there an intentional purpose for using “make” and aliasing it with “default”, instead of using “default” directly?

Yes, if you’d call make just default, you’d not be able to use <MyComp> in your ReScript JSX.

That’s because in ReScript, <MyComp name="test"> desugars to React.createElement(MyComp.make, MyComp.makeProps(~name="test", ())), so make is a mandatory interface name.

If you don’t care about ReScript JSX usage, you don’t need to call your function make.

1 Like

Thanks. I should have re-read the reasonreact docs before asking that. Will consult them first next time

Any reason why compiler desugars it to make and not default?

Because the builtin react-ppx always uses the original binding name and doesn’t try to do any additional magic:

// desugars to `make` and `makeProps`
@react.component
let make = (name: string) => { ... }

// desugars to `default` and `defaultProps`
@react.component
let default = (name: string) => { ... }

Edit: The reason why the make convention exists is a) it was a common pattern to call a constructor make, and b) it would be kinda weird to automatically export to default for many different reasons. A user should be in control to decide what should be exported as default, and not unintentionally export something as default I assume

2 Likes

My first instinct for dealing with such a problem is to convert data to Json.t and the convert back to the ReScript type when receiving the Json.t later.

Is genType a simpler approach that avoids the need to convert to Json.t explicitly?

In many cases, yes. Also, if your JavaScript is typed with flow or typescript, then it’s also potentially safer than relying on JSON.parse.