Docs / ReasonReact / Interop

Talk to Existing ReactJS Code

The Record API is in feature-freeze. For the newest features and better support going forward, please consider migrating to the new function components.

Project Setup

You can reuse the same bsb setup (that you might have seen here)! Aka, put a bsconfig.json at the root of your ReactJS project:

JSON
{ "name": "my-project-name", "reason": {"react-jsx" : 2}, "sources": [ "my_source_folder" ], "package-specs": [{ "module": "commonjs", "in-source": true }], "suffix": ".bs.js", "namespace": true, "bs-dependencies": [ "reason-react" ], "refmt": 3 }

This will build Reason files in my_source_folder (e.g. reasonComponent.re) and output the JS files (e.g. reasonComponent.bs.js) alongside them.

Then add bs-platform to your package.json (npm install --save-dev bs-platform or yarn add --dev bs-platform):

JSON
"scripts": { "start": "bsb -make-world -w" }, "devDependencies": { "bs-platform": "^2.1.0" }, "dependencies": { "react": "^15.4.2", "react-dom": "^15.4.2", "reason-react": "^0.3.1" } ...

Running npm start (or alias it to your favorite command) starts the bsb build watcher. You don't have to touch your existing JavaScript build configuration!

Usage

A ReasonReact record component is not a ReactJS component. We provide hooks to communicate between the two.

Whether you're using an existing ReactJS component or providing a ReasonReact component for consumption on the JS side, you need to establish the type of the JS props you'd convert from/to, by using BuckleScript's bs.deriving abstract:

RE
[@bs.deriving abstract] type jsProps = { /* some example fields */ className: string, /* `type` is reserved in Reason. use `type_` and make it still compile to the JS key `type` */ [@bs.as "type"] type_: string, value: Js.nullable(int), };

This will generate the getters and the JS object creation function (of the same name, jsProps) you'll need.

Note: you do not declare ref and key (the two special ReactJS "props"). We handle that for you, just like ReactJS does. They're not really props.

ReasonReact using ReactJS

Easy! Since other Reason components only need you to expose a make function, fake one up:

RE
[@bs.module] external myJSReactClass: ReasonReact.reactClass = "./myJSReactClass"; let make = (~className, ~type_, ~value=?, children) => ReasonReact.wrapJsForReason( ~reactClass=myJSReactClass, ~props=jsProps( ~className, ~type_, ~value=Js.Nullable.fromOption(value), ), children, );

ReasonReact.wrapJsForReason is the helper we expose for this purpose. It takes in:

  • The reactClass you want to wrap

  • The props js object you'd create through the generated jsProps function from the jsProps type you've declared above (with values properly converted from Reason data structures to JS)

  • The mandatory children you'd forward to the JS side.

props is mandatory. If you don't have any to pass, pass ~props=Js.Obj.empty() instead.

Note: if your app successfully compiles, and you see the error "element type is invalid..." in your console, you might be hitting this mistake.

ReactJS Using ReasonReact

Eeeeasy. We expose a helper for the other direction, ReasonReact.wrapReasonForJs:

RE
let component = ...; let make ...; [@bs.deriving abstract] type jsProps = { name: string, age: Js.nullable(int), }; let jsComponent = ReasonReact.wrapReasonForJs(~component, jsProps => make( ~name=jsProps->nameGet, ~age=?Js.Nullable.toOption(jsProps->ageGet), [||], ) );

The function takes in:

  • The labeled reason component you've created

  • A function that, given the JS props, asks you to call make while passing in the correctly converted parameters (bs.deriving abstract above generates a field accessor for every record field you've declared).

Note the jsProps->nameGet and jsProps->ageGet part. This is a getter generated by bs.deriving abstract. Documentations here.

You'd assign the whole thing to the name jsComponent. The JS side can then import it:

var MyReasonComponent = require('./myReasonComponent.bs').jsComponent; // make sure you're passing the correct data types! <MyReasonComponent name="John" />

Note: if you'd rather use a default import on the JS side, you can export such default from BuckleScript/ReasonReact:

RE
let default = ReasonReact.wrapReasonForJs(...)

and then import it on the JS side with:

import MyReasonComponent from './myReasonComponent.bs';

BuckleScript default exports only works when the JS side uses ES6 import/exports. More info here.