title | description | canonical |
---|---|---|
Beyond JSX |
Details on how to use ReScript and React without JSX |
/docs/react/latest/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: The output shown for the examples on this page assumes your
rescript.json
to be set to"jsx": { "version": 4, "mode": "classic" }
. We will update it for automatic mode soon.
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):
// 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.
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:
module Friend = {
@react.component
let make = (~name: string, ~age: int) => {React.string(name ++ ", " ++ age->Int.toString)}
}
module Container = {
@react.component
let make = (~width: int, ~children) => {<> {React.string(width->Int.toString)} children </>}
}
<CodeTab labels={["Decorated", "Expanded", "JS Output"]}>
module Friend = {
@react.component
let make = (~name: string, ~children) => {
<div>
{React.string(name)}
children
</div>
}
}
module Friend = {
type props<'name, 'children> = {
name: 'name,
children: 'children,
}
let make = ({name, children, _}: props<string, 'children>) => {
ReactDOM.createDOMElementVariadic("div", [{React.string(name)}, children])
}
}
function Playground$Friend(props) {
return JsxRuntime.jsxs("div", {
children: [
props.name,
props.children
]
});
}
var Friend = {
make: Playground$Friend
};
In the expanded output:
props
: A generated record type that has fields according to the labeled arguments of themake
functionmake
: A convertedmake
function that complies to the component interface(props) => React.element
The @react.component
decorator also works for React.forwardRef
calls:
<CodeTab labels={["Decorated", "Expanded"]}>
module FancyInput = {
@react.component
let make = React.forwardRef((~className=?, ~children, ref) =>
<div>
// use ref here
</div>
)
}
// Simplified Output
type props<'className, 'children, 'ref> = {
className?: 'className,
children: 'children,
ref?: 'ref,
}
let make = (
{?className, children, _}: props<'className, 'children, ReactDOM.Ref.currentDomRef>,
ref: Nullable.t<ReactDOM.Ref.currentDomRef>,
) => make(~className, ~children, ~ref, ())
As shown in the expanded output above, our decorator desugars the function passed to React.forwardRef
in the same manner as a typical component make
function. It also creates a props
type with an optional ref
field, so we can use it in our JSX call (<FancyInput ref=.../>
).
So now that we know how the ReScript React component transformation works, let's have a look on how ReScript transforms our JSX constructs.
Whenever we are using JSX with a custom component ("capitalized JSX"), we are actually using React.createElement
to create a new element. Here is an example of a React component without children:
<CodeTab labels={["JSX", "Without JSX", "JS Output"]}>
let _ = <Friend name="Fred" age=20 />
// classic
React.createElement(Friend.make, {name: "Fred", age:20})
// automatic
React.jsx(Friend.make, {name: "Fred", age: 20})
JsxRuntime.jsx(Playground$Friend, { name: "Fred", age: 20 });
As you can see, it uses Friend.make
to call the React.createElement
API. In case you are providing children, it will use React.createElementVariadic
instead (which is just a different binding for React.createElement
):
<CodeTab labels={["JSX", "Without JSX", "JS Output"]}>
<Container width=200>
{React.string("Hello")}
{React.string("World")}
</Container>
// classic
React.createElementVariadic(
Container.make,
{width: 200, children: React.null},
[{React.string("Hello")}, {React.string("World")}],
)
// automatic
React.jsxs(
Container.make,
{width: 200, children: React.array([{React.string("Hello")}, {React.string("World")}])},
)
JsxRuntime.jsx(Container, { width: 200, children: null }, "Hello", "World");
Note that the children: React.null
field has no relevance since React will only care about the children array passed as a third argument.
"Uncapitalized JSX" expressions are treated as DOM elements and will be converted to ReactDOM.createDOMElementVariadic
calls:
<CodeTab labels={["JSX", "Without JSX", "JS Output"]}>
<div title="test"/>
// classic
ReactDOM.createDOMElementVariadic("div", ~props={title: "test"}, [])
// automatic
ReactDOM.jsx("div", {title: "test"})
JsxRuntime.jsx("div", { title: "test" });
The same goes for uncapitalized JSX with children:
<CodeTab labels={["JSX", "Without JSX", "JSX Output", "Without JSX Output"]}>
<div title="test">
<span/>
</div>
// classic
ReactDOM.createDOMElementVariadic(
"div",
~props={title: "test"},
[ReactDOM.createDOMElementVariadic("span", [])],
)
// automatic
ReactDOM.jsx("div", {title: "test", children: ?ReactDOM.someElement(ReactDOM.jsx("span", {}))})
JsxRuntime.jsx("div", {
children: JsxRuntime.jsx("span", {}),
title: "test"
});
React.createElement("div", {
title: "test"
}, React.createElement("span", undefined));