JSX

Would you like some HTML syntax in your ReScript? If not, quickly skip over this section and pretend you didn't see anything!

ReScript supports the JSX syntax, with some slight differences compared to the one in ReactJS. ReScript JSX isn't tied to ReactJS; they translate to normal function calls:

Note for ReasonReact readers: this isn't what ReasonReact turns JSX into, in the end. See Usage section for more info.

Capitalized Tag

ReScriptJS Output
<MyComponent name={"ReScript"} />

becomes

ReScriptJS Output
@JSX MyComponent.createElement(~name="ReScript", ~children=list{}, ())

Uncapitalized Tag

ReScriptJS Output
<div onClick={handler}> child1 child2 </div>

becomes

ReScriptJS Output
@JSX div(~onClick=handler, ~children=list{child1, child2}, ())

Fragment

ReScriptJS Output
<> child1 child2 </>

becomes

ReScriptJS Output
@JSX list{child1, child2}

Children

ReScriptJS Output
<MyComponent> child1 child2 </MyComponent>

This is the syntax for passing a list of two items, child1 and child2, to the children position. It desugars to a list containing child1 and child2:

ReScriptJS Output
@JSX MyComponent.createElement(~children=list{child1, child2}, ())

Note again that this isn't the transform for ReasonReact; ReasonReact turns the final list into an array. But the idea still applies.

So naturally, <MyComponent> myChild </MyComponent> desugars to @JSX MyComponent.createElement(~children=list{myChild}, ()). I.e. whatever you do, the arguments passed to the children position will be wrapped in a list. What if you don't want that? What if you want to directly pass myChild without an extra wrapping?

Children Spread

To solve the above problem, we've introduced

ReScriptJS Output
<MyComponent> ...myChild </MyComponent>

This passes the value myChild without wrapping it in a list (or array, in the case of ReasonReact). Aka, this desugars to:

ReScriptJS Output
@JSX MyComponent.createElement(~children=myChild, ())

This is extra useful in the cases where you are handled myChild that is already a list of things, and want to forward that without wrapping it an extra time (which would be a type error) *. It also allows you to pass arbitrary data structures at children position (remember, JSX children is really just a totally normal prop):

ReScriptJS Output
<MyComponent> ...((theClassName) => <div className=theClassName />) </MyComponent>

<MyForm> ...("Hello", "Submit") </MyForm>

Usage

See ReasonReact JSX for an example application of JSX, which transforms the above calls into a ReasonReact-specific call.

Here's a JSX tag that shows most of the features.

ReScriptJS Output
<MyComponent
  booleanAttribute={true}
  stringAttribute="string"
  intAttribute=1
  forcedOptional=?{Some("hello")}
  onClick={handleClick}>
  <div> {React.string("hello")} </div>
</MyComponent>

Departures From JS JSX

  • Attributes and children don't mandate {}, but we show them anyway for ease of learning. Once you format your file, some of them go away and some turn into parentheses.

  • There is no support for JSX prop spread: <Comp {...props} />. Though somewhat related, we do have children spread, described above: <Comp> ...children </Comp>.

  • Punning!

Punning

"Punning" refers to the syntax shorthand for when a label and a value are the same. For example, in JavaScript, instead of doing return {name: name}, you can do return {name}.

Reason JSX supports punning. <input checked /> is just a shorthand for <input checked=checked />. The formatter will help you format to the punned syntax whenever possible. This is convenient in the cases where there are lots of props to pass down:

ReScriptJS Output
<MyComponent isLoading text onClick />

Consequently, a Reason JSX component can cram in a few more props before reaching for extra libraries solutions that avoids props passing.

Note that this is a departure from ReactJS JSX, which does not have punning. ReactJS' <input checked /> desugars to <input checked=true />, in order to conform to DOM's idioms and for backward compatibility.

Tip & Tricks

For library authors wanting to take advantage of the JSX: the @JSX attribute above is a hook for potential ppx macros to spot a function wanting to format as JSX. Once you spot the function, you can turn it into any other expression.

This way, everyone gets to benefit the JSX syntax without needing to opt into a specific library using it, e.g. ReasonReact.

JSX calls supports the features of labeled arguments: optional, explicitly passed optional and optional with default.

Design Decisions

* You might wonder why you never needed such children spread in ReactJS; ReactJS uses some special runtime logic + special syntax transforms + variadic argument detection & marking to avoid most of these cases (see here). Such dynamic usage complexifies the type system detection quite a bit. Since we control the whole syntax and ReasonReact, we decided to introduce children spread to disambiguate between the case of wrapping vs not wrapping, without compile-time & runtime cost!