Skip to content

Commit 9e6e19e

Browse files
committed
Refine JSX / rendering elements / components section. Remove JSX transformation page
1 parent 0a0bf9f commit 9e6e19e

File tree

5 files changed

+353
-99
lines changed

5 files changed

+353
-99
lines changed
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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: "Main Concepts"
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 apis for [creating elements](./rendering-elements#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>

pages/docs/react/latest/components-and-props.mdx

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,50 @@ var make = App;
315315

316316
</CodeTab>
317317

318-
**Note:** React components are capitalized; primitive DOM elements like `div` or `button` are uncapitalized. More infos on the JSX specifics and code transformations can be found in our [JSX section](/docs/manual/latest/jsx#capitalized-tag) in our language manual.
318+
**Note:** React components are capitalized; primitive DOM elements like `div` or `button` are uncapitalized. More infos on the JSX specifics and code transformations can be found in our [JSX language manual section](/docs/manual/latest/jsx#capitalized-tag).
319319

320-
More details on the `@react.component` decorator and its generated interface can be found in our [Advanced React JSX Transformation](./react-jsx-transformation) page.
320+
321+
### Handwritten Components
322+
323+
You don't need to use the `@react.component` decorator to write components that can be used in JSX. Instead you can write a pair of `make` and `makeProps` functions such that type `makeProps: 'a => props` and `make: props => React.element` and these will always work as React components.
324+
325+
This works with your own version of `[@bs.obj]`, or any other function that takes named args and returns a single props structure. For example:
326+
327+
<CodeTab labels={["ReScript", "JS Output"]}>
328+
329+
```res
330+
module Link = {
331+
type props = {"href": string, "children": React.element};
332+
@bs.obj external makeProps:(
333+
~href: string,
334+
~children: React.element,
335+
unit) => props = ""
336+
337+
let make = (props: props) => {
338+
<a href={props["href"]}>
339+
{props["children"]}
340+
</a>
341+
}
342+
}
343+
344+
<Link href="/docs"> {React.string("Docs")} </Link>
345+
```
346+
```js
347+
function Link(props) {
348+
return React.createElement("a", {
349+
href: props.href
350+
}, props.children);
351+
}
352+
353+
React.createElement(Link, {
354+
href: "/docs",
355+
children: "Docs"
356+
});
357+
```
358+
359+
</CodeTab>
360+
361+
More details on the `@react.component` decorator and its generated interface can be found in our [Beyond JSX](./beyond-jsx) page.
321362

322363

323364
## Submodule Components
@@ -342,7 +383,7 @@ let make = (~children) => {
342383
}
343384
```
344385

345-
The `Button.res` file defined in above is now containing a `Label` component, that can also be used by other components, either by writing the fully qualified module name (`<Button.Label title="My Button"/>`) or by using a module alias to shortcut the full qualifier:
386+
The `Button.res` file defined above is now containing a `Label` component, that can also be used by other components, either by writing the fully qualified module name (`<Button.Label title="My Button"/>`) or by using a module alias to shortcut the full qualifier:
346387

347388

348389
```res
@@ -351,6 +392,31 @@ module Label = Button.Label
351392
let content = <Label title="Test"/>
352393
```
353394

395+
## Component Naming
396+
397+
Because components are actually a pair of functions, they have to belong to a module to be used in JSX. It makes sense to use these modules for identification purposes as well. `@react.component` automatically adds the name for you based on the module you are in.
398+
399+
400+
```res
401+
// File.re
402+
403+
// will be named `File` in dev tools
404+
[@react.component]
405+
let make = ...
406+
407+
// will be named `File$component` in dev tools
408+
[@react.component]
409+
let component = ...
410+
411+
module Nested = {
412+
// will be named `File$Nested` in dev tools
413+
[@react.component]
414+
let make = ...
415+
};
416+
```
417+
418+
If you need a dynamic name for higher-order components or you would like to set your own name you can use `React.setDisplayName(make, "NameThatShouldBeInDevTools")`.
419+
354420

355421
## Tips & Tricks
356422

pages/docs/react/latest/react-jsx-transformation.mdx

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)