Beyond JSX

JSX is a syntax sugar that allows us to use React components in an HTML like manner. A component needs to adhere to certain interface conventions, otherwise it can't be used in JSX. This section will go into detail on how the JSX transformation works and what React APIs are used underneath.

Note: This section requires knowledge about the low level apis for creating elements, such as React.createElement or ReactDOM.createDOMElementVariadic.

Note: These examples use "jsx": { "version": 4 } in rescript.json. ReScript v12 generates calls to React's automatic JSX runtime, using jsx for zero or one child and jsxs for multiple children.

Component Types

A plain React component is defined as a ('props) => React.element function. You can also express a component more efficiently with our shorthand type React.component<'props>.

Here are some examples on how to define your own component types (often useful when interoping with existing JS code, or passing around components):

RES
// Plain function type type friend = {name: string, online: bool} type friendComp = friend => React.element // Equivalent to // ({padding: string, children: React.element}) => React.element type props = {padding: string, children: React.element} type containerComp = React.component<props>

The types above are pretty low level (basically the JS representation of a React component), but since ReScript React has its own ways of defining React components in a more language specific way, let's have a closer look on the anatomy of such a construct.

JSX Component Interface

A ReScript React component needs to be a (sub-)module with a make function and props type to be usable in JSX. To make things easier, we provide a @react.component decorator to create those functions for you:

DecoratedWithout DecoratorJS OutputJSX Preserved Output
module Friend = {
  @react.component
  let make = (~name: string, ~children) => {
    <div>
      {React.string(name)}
      children
    </div>
  }
}

The decorator produces the same basic interface:

  • props: A generated record type that has fields according to the labeled arguments of the make function

  • make: A converted make function that complies to the component interface (props) => React.element

Ref Props

React 19 function components can receive ref as a normal prop. The decorator supports a ~ref argument that you can pass to a DOM element, as shown in Refs and the DOM.

The decorator also supports React.forwardRef for existing components that use that API.

So now that we know how the ReScript React component transformation works, let's have a look on how ReScript transforms our JSX constructs.

JSX Under the Hood

JSX with a custom component ("capitalized JSX") uses React.jsx or React.jsxs, which call the corresponding functions from react/jsx-runtime. Here is an example of a React component without children:

JSXWithout JSXJS OutputJSX Preserved Output
let _ = <Friend name="Fred" age=20 />

The component function is Friend.make, and the props are passed as a record. With multiple children, JSX uses React.jsxs and includes them in the children prop:

JSXWithout JSXJS OutputJSX Preserved Output
<Container width=200>
  {React.string("Hello")}
  {React.string("World")}
</Container>

React.createElement and React.createElementVariadic remain available for manually creating elements. ReScript v12's JSX transformation uses the automatic runtime functions shown above.

Dom Elements

"Uncapitalized JSX" expressions are treated as DOM elements and use ReactDOM.jsx or ReactDOM.jsxs:

JSXWithout JSXJS OutputJSX Preserved Output
<div title="test"/>

The same goes for uncapitalized JSX with children:

JSXWithout JSXJS OutputJSX Preserved Output
<div title="test">
  <span/>
</div>