Using a custom jsx function

I’m brand new to Reason/ReScript and I’m evaluating how our team might use Reason in our React project.

We’ve hit one major stumbling block. Our project uses Theme UI for CSS-in-JS styling, which works like this:

  1. Each styled component gets a special sx prop that contains a style object. E.g., <div sx={{ margin: '4px' }} /> .
  2. Each .jsx/.tsx file that has sx props in it is marked with a special JSX pragma at the top (e.g., /** @jsx jsx */ ), which tells Babel to use jsx(...) calls instead of React.createElement(...). This behavior is part of Babel, not Theme UI, and you can use whatever function you like (in Babel’s docs, they use Preact.h). Theme UI’s jsx is a function like React.createElement that also knows how to handle the sx prop. It’s imported from Theme UI like so: import { jsx } from 'theme-ui' .

So basically, I need to figure out how to use Theme UI’s jsx function to create my elements rather than React’s createElement. I also need to figure out how to get sx into the prop type for JSX elements, but that’s another battle.

My first hunch was that the limitation had to do with ReasonReact, so I opened an issue.

I realized later that the limitation is at a deeper level, with something called the JSX ppx. It appears that ppx’s are an OCaml mechanism for extending the language through AST preprocessing?

I looked up that particular ppx. It appears that calls to React.createElement are hardcoded, but it’s hard to tell if that’s a default or if it’s the only option.

Then, I came across this bit of documentation in the ReScript manual (under JSX->Tips and 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.

I’m not sure what “spot a function” means, but I naively tried writing @JSX div(~onClick=handler, ~children=list{child1, child2}, ()) and got this error: Unbound constructor JSX.

I tried searching the ReasonReact source code for any mention of “jsx” and I couldn’t find any. I see the "reason": { "react-jsx": 3 } in bsconfig.json, but I’m not sure how the ppx in ReScript and ReasonReact are connected, nor how I could leverage the ppx to support my own use case (i.e., Theme UI’s jsx function).

Does anyone have an idea for how to tackle this problem? Thanks!

1 Like

I’m not familiar with theme-ui, but even though you use rescript, you’d probably still use Babel and a bundler.
If Babel is replacing actual createElement calls and you wany to reuse your existing setup, then you could just add a raw statement at the top of your file including your pragma like:

Unfortunately, that won’t work.

Babel isn’t replacing createElement calls in the sense that it’s doing a first pass over the JSX and inserting React.createElement, then doing a second pass over the transformed JSX and replacing it with jsx. It’s doing a single pass over the JSX and inserting whatever is in the jsx pragma. Since ReScript already translates the JSX into JS code, adding the pragma to the generated file won’t have any effect–it only affects how Babel transforms JSX into JS.

Here’s the documentation from Babel: They use Preact.h as their example. Anything that goes into the /** @jsx ___ */ pragma gets used instead of React.createElement. That’s what I’m looking to achieve, except with ReScript.

Edited my explanation to make the Babel step clearer.

You would most likely need to write your own jsx ppx variant (compiled as a native binary) and hook it up with the compiler via bsconfig.json to be able to express this kind of JSX.

There is also an issue that a React.element such as <div> doesn’t type check if you apply a sx attribute to them, so not even sure how this would be fixable?