How to write rescript bindings for a react library

·

3 min read

Bindings are nothing but FFI

According to wiki:

A foreign function interface (FFI) is a mechanism by which a program written in one programming language can call routines or make use of services written in another.

ReScript bindings are just an unsafe direct-access mechanism to the JS world. Just some external declarations and some type definitions.

In this post, we are going to write few bindings for the antd library.

Let's get started with a very simple component that doesn't accept any props or children.

Named Import

Component

// closeCircleOutlined.jsx
import { CloseOutlined } from '@ant-design/icons'
/* npm install --save @ant-design/icons */

ReactDOM.render(
 <CloseOutlined />,
  mountNode,
);

Binding:

// Bindings.res
module CloseCircleOutlined = {
 @module("@ant-design/icons") @react.component
 external make: React.element = "CloseCircleOutlined"
}

Default Import

// Bindings.res

// import CloseOutlined from '@ant-design/icons'
module CloseOutlined = {
 @module("@ant-design/icons") @react.component
 external make: React.element = "default"
}

I'm assuming you know what decorators are. If you haven't read about them before then you can read here

The ** basic structure ** is

module ComponentName = {
 @module("<node_module_name>") @react.component
 external make: React.element = "<NamedImport> or <default>"
}

This component doesn't accept any props yet.

Let's write another binding for a button.

Component:

import { Button } from 'antd';

ReactDOM.render(
  <>
    <Button shape="circle">Circle Button</Button>
    <Button shape="round">Round Button</Button>
  </>,
  mountNode,
);

Binding:

Copy the structure and fill in the names.

// Bindings.res
module Button = {
 @module("antd") @react.component
 external make: React.element = "Button"
}

At this point, you can only use the button as

<Button/>

not as

<Button shape="circle">Text</Button>

Props

Let's add a shape prop.

// Bindings.res
module Button = {
 @module("antd") @react.component
 external make: (~shape:string) => React.element = "Button"
}

Remember, we have to declare each prop as a Named argument.

Keyword prop

Now, here is a little tricky one. Let's add a type prop.

// Bindings.res
module Button = {
 @module("antd") @react.component
 external make: (~\"type": string, ~shape:string) => React.element = "Button"
}

type is a keyword in a rescript so whenever we use a keyword we have to escape it.

Children

To accept the child component, we will use children named argument

// Bindings.res
module Button = {
 @module("antd") @react.component
 external make: (~children:React.element, ~shape:string, ~\"type": string) => React.element = "Button"
}
// App.res
ReactDOM.render(
 <Button \"type"="primary" shape="circle">{React.string("Click me")}</Button>, 
 mountNode
)

React prop

// Bindings.res
module Button = {
 @module("antd") @react.component
 external make: (~icon: React.element, ~children:React.element, ~shape:string, ~\"type": string) => React.element = "Button"
}
// App.res
ReactDOM.render(
 <Button icon={<DownloadOutlined />} shape="circle" \"type"="primary">{React.string("Click me")}</Button>, 
 mountNode
)

That's all folks!