|
| 1 | +--- |
| 2 | +title: Beyond JSX |
| 3 | +description: "Details on how to use ReScript and React without JSX" |
| 4 | +canonical: "/docs/react/latest/beyond-jsx" |
| 5 | +category: "Guides" |
| 6 | +--- |
| 7 | + |
| 8 | +# Beyond JSX |
| 9 | + |
| 10 | +<Intro> |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +</Intro> |
| 15 | + |
| 16 | +**Note:** This section requires knowledge about the low level apis for [creating elements](./elements-and-jsx#creating-elements-from-component-functions), such as `React.createElement` or `ReactDOMRe.createDOMElementVariadic`. |
| 17 | + |
| 18 | +## Component Types |
| 19 | + |
| 20 | +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)`. |
| 21 | + |
| 22 | +Here are some examples on how to define your own component types (often useful when interoping with existing JS code, or passing around components): |
| 23 | + |
| 24 | +```res |
| 25 | +// Plain function type |
| 26 | +type friendComp = |
| 27 | + ({"name": string, "online": bool}) => React.element; |
| 28 | +
|
| 29 | +// Equivalent to |
| 30 | +// ({"padding": string, "children": React.element}) => React.element |
| 31 | +type containerComp = |
| 32 | + React.component({ |
| 33 | + "padding": string, |
| 34 | + "children": React.element |
| 35 | + }); |
| 36 | +``` |
| 37 | +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. |
| 38 | + |
| 39 | +## JSX Component Interface |
| 40 | + |
| 41 | +A ReScript React component needs to be a (sub-)module with a `make` and `makeProps` function to be usable in JSX. To make things easier, we provide a `@react.component` decorator to create those functions for you: |
| 42 | + |
| 43 | +<CodeTab labels={["Decorated", "Expanded"]}> |
| 44 | + |
| 45 | +```res |
| 46 | +module Friend = { |
| 47 | + @react.component |
| 48 | + let make = (~name: string, ~children) => { |
| 49 | + <div> |
| 50 | + {React.string(name)} |
| 51 | + children |
| 52 | + </div> |
| 53 | + } |
| 54 | +} |
| 55 | +``` |
| 56 | +```res |
| 57 | +module Friend = { |
| 58 | + [@bs.obj] |
| 59 | + external makeProps: ( |
| 60 | + ~name: string, |
| 61 | + ~children: 'children, |
| 62 | + ~key: string=?, |
| 63 | + unit) => {. "name": string, "children": 'children'} = ""; |
| 64 | +
|
| 65 | + let make = (props: {. "name": string, "children": 'children}) => { |
| 66 | + // React element creation from the original make function |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +</CodeTab> |
| 72 | + |
| 73 | +In the expanded output: |
| 74 | + |
| 75 | +- `makeProps`: A function that receives multiple labeled arguments (according to prop names) and returns the value that is consumed by make(props) |
| 76 | +- `make`: A converted `make` function that complies to the component interface `(props) => React.element` |
| 77 | + |
| 78 | +**Note:** The `makeProps` function will also always contain a `~key` prop. |
| 79 | + |
| 80 | +### Special Case React.forwardRef |
| 81 | + |
| 82 | +The `@react.component` decorator also works for `React.forwardRef` calls: |
| 83 | + |
| 84 | + |
| 85 | +<CodeTab labels={["Decorated", "Expanded"]}> |
| 86 | + |
| 87 | +```res |
| 88 | +module FancyInput = { |
| 89 | + @react.component |
| 90 | + let make = React.forwardRef((~className=?, ~children, ref_) => |
| 91 | + <div> |
| 92 | + // use ref_ here |
| 93 | + </div> |
| 94 | + ) |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +```res |
| 99 | +// Simplified Output |
| 100 | +module FancyInput = { |
| 101 | + @bs.obj |
| 102 | + external makeProps: ( |
| 103 | + ~className: 'className=?, |
| 104 | + ~children: 'children, |
| 105 | + ~key: string=?, |
| 106 | + ~ref: 'ref=?, |
| 107 | + unit, |
| 108 | + ) => {"className": option<'className>, "children": 'children} = "" |
| 109 | +
|
| 110 | + let make = |
| 111 | + (~className=?, ~children) => ref_ => ReactDOMRe.createDOMElementVariadic("div", []) |
| 112 | +
|
| 113 | + let make = React.forwardRef( |
| 114 | + (props: {"className": option<'className>, "children": 'children}, ref_,) => { |
| 115 | + make( |
| 116 | + ~className=props["className"], |
| 117 | + ~children=props["children"], |
| 118 | + ref_) |
| 119 | + }) |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +</CodeTab> |
| 124 | + |
| 125 | +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 `makeProps` function with a `ref` prop, so we can use it in our JSX call (`<FancyInput ref=.../>`). |
| 126 | + |
| 127 | +So now that we know how the ReScript React component transformation works, let's have a look on how ReScript transforms our JSX constructs. |
| 128 | + |
| 129 | +## JSX Under the Hood |
| 130 | + |
| 131 | +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: |
| 132 | + |
| 133 | +<CodeTab labels={["JSX", "Without JSX"]}> |
| 134 | + |
| 135 | +```res |
| 136 | +<Friend name="Fred" age=1 /> |
| 137 | +``` |
| 138 | +```res |
| 139 | +React.createElement(Friend.make, Friend.makeProps(~name="Fred", ~age=1, ())) |
| 140 | +``` |
| 141 | +```js |
| 142 | +React.createElement(Playground$Friend, { name: "Fred", age: 20 }); |
| 143 | +``` |
| 144 | + |
| 145 | +</CodeTab> |
| 146 | + |
| 147 | +As you can see, it uses `Friend.make` and `Friend.makeProps` 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`): |
| 148 | + |
| 149 | +<CodeTab labels={["JSX", "Without JSX", "JS Output"]}> |
| 150 | + |
| 151 | +```res |
| 152 | +<Container width=200> |
| 153 | + {React.string("Hello")} |
| 154 | + {React.string("World")} |
| 155 | +</Container> |
| 156 | +``` |
| 157 | + |
| 158 | +```res |
| 159 | +React.createElementVariadic( |
| 160 | + Container.make, |
| 161 | + Container.makeProps(~width=200, ~children=React.null, ()), |
| 162 | + [{React.string("Hello")}, {React.string("World")}], |
| 163 | +) |
| 164 | +``` |
| 165 | + |
| 166 | +```js |
| 167 | +React.createElement(Container, { width: 200, children: null }, "Hello", "World"); |
| 168 | +``` |
| 169 | + |
| 170 | +</CodeTab> |
| 171 | + |
| 172 | +Note that the `~children=React.null` prop has no relevance since React will only care about the children array passed as a third argument. |
| 173 | + |
| 174 | + |
| 175 | +### Dom Elements |
| 176 | + |
| 177 | +"Uncapitalized JSX" expressions are treated as DOM elements and will be converted to `ReactDOMRe.createDOMElementVariadic` calls: |
| 178 | + |
| 179 | +<CodeTab labels={["JSX", "Without JSX", "JS Output"]}> |
| 180 | + |
| 181 | +```res |
| 182 | +<div title="test"/> |
| 183 | +``` |
| 184 | + |
| 185 | +```res |
| 186 | +ReactDOMRe.createDOMElementVariadic("div", ~props=ReactDOMRe.domProps(~title="test", ()), []) |
| 187 | +``` |
| 188 | + |
| 189 | +```js |
| 190 | + React.createElement("div", { title: "test" }); |
| 191 | +``` |
| 192 | + |
| 193 | +</CodeTab> |
| 194 | + |
| 195 | +The same goes for uncapitalized JSX with children: |
| 196 | + |
| 197 | +<CodeTab labels={["JSX", "Without JSX", "JS Output"]}> |
| 198 | + |
| 199 | +```res |
| 200 | +<div title="test"> |
| 201 | + <span/> |
| 202 | +</div> |
| 203 | +``` |
| 204 | + |
| 205 | +```res |
| 206 | +ReactDOMRe.createDOMElementVariadic( |
| 207 | + "div", |
| 208 | + ~props=ReactDOMRe.domProps(~title="test", ()), |
| 209 | + [ReactDOMRe.createDOMElementVariadic("span", [])], |
| 210 | +) |
| 211 | +``` |
| 212 | + |
| 213 | +```js |
| 214 | +React.createElement("div", { title: "test" }, React.createElement("span", undefined)); |
| 215 | +``` |
| 216 | + |
| 217 | +</CodeTab> |
0 commit comments