Skip to content

Commit c5ea779

Browse files
committed
Document optional props
1 parent aee491c commit c5ea779

File tree

1 file changed

+169
-1
lines changed

1 file changed

+169
-1
lines changed

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

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,180 @@ var make = Article;
9090

9191
### Optional Props
9292

93+
We can leverage the full power of labeled arguments to define optional props as well:
94+
95+
<CodeTab labels={["ReScript", "JS Output"]}>
96+
97+
```res
98+
// Greeting.res
99+
@react.component
100+
let make = (~name: option<string>=?) => {
101+
let greeting = switch name {
102+
| Some(name) => "Hello " ++ name ++ "!"
103+
| None => "Hello stranger!"
104+
}
105+
<div> {React.string(greeting)} </div>
106+
}
107+
```
108+
109+
```js
110+
function Greeting(Props) {
111+
var name = Props.name;
112+
var greeting = name !== undefined ? "Hello " + name + "!" : "Hello stranger!";
113+
return React.createElement("div", undefined, greeting);
114+
}
115+
```
116+
117+
</CodeTab>
118+
119+
**Note:** The `@react.component` attribute implicitly adds the last `()` parameter to our `make` function for us (no need to do it ourselves).
120+
121+
In JSX, you can apply optional props with some special syntax:
122+
123+
<CodeTab labels={["ReScript", "JS Output"]}>
124+
125+
```res
126+
let name = Some("Andrea")
127+
128+
<Greeting ?name />
129+
```
130+
131+
```js
132+
var Caml_option = require("./stdlib/caml_option.js");
133+
var name = "Andrea";
134+
135+
var tmp = {};
136+
137+
if (name !== undefined) {
138+
tmp.name = Caml_option.valFromOption(name);
139+
}
140+
141+
var greeting = React.createElement(Playground$Greeting, tmp);
142+
```
143+
144+
</CodeTab>
145+
146+
### Children Props
147+
148+
In React `props.children` is a special attribute to represent the nested elements within a parent element:
149+
150+
```res
151+
let element = <div> child1 child2 </div>
152+
```
153+
154+
By default, whenever you are passing children like in the expression above, `children` will be treated
155+
as a `React.element`:
156+
157+
<CodeTab labels={["ReScript", "JS Output"]}>
158+
159+
```res
160+
module MyList = {
161+
@react.component
162+
let make = (~children: React.element) => {
163+
<ul>
164+
children
165+
</ul>
166+
}
167+
}
168+
169+
<MyList>
170+
<li> {React.string("Item 1")} </li>
171+
<li> {React.string("Item 2")} </li>
172+
</MyList>
173+
```
174+
175+
```js
176+
177+
function MyList(Props) {
178+
var children = Props.children;
179+
return React.createElement("ul", undefined, children);
180+
}
181+
182+
var MyList = {
183+
make: MyList
184+
};
185+
186+
React.createElement(MyList, {
187+
children: null
188+
}, React.createElement("li", undefined, "Item 1"),
189+
React.createElement("li", undefined, "Item 2"));
190+
```
191+
192+
</CodeTab>
193+
194+
Interestingly, it doesn't matter if you are passing just one element, or several, React will always collapse its children to a single `React.element`.
195+
196+
It is also possible to redefine the `children` type as well. Here are some examples:
197+
198+
**Component with a mandatory `string` as children:**
199+
200+
```res
201+
module StringChildren = {
202+
@react.component
203+
let make = (~children: string) => {
204+
<div>
205+
{React.string(children)}
206+
</div>
207+
}
208+
}
209+
210+
<StringChildren> "My Child" </StringChildren>
211+
212+
// This will cause a type check error
213+
<StringChildren/>
214+
```
215+
216+
**Component with an optional `React.element` as children:**
217+
218+
```res
219+
module OptionalChildren = {
220+
@react.component
221+
let make = (~children: option<React.element>=?) => {
222+
<div>
223+
{switch children {
224+
| Some(element) => element
225+
| None => React.string("No children provided")
226+
}}
227+
</div>
228+
}
229+
}
230+
231+
<div>
232+
<OptionalChildren />
233+
<OptionalChildren> <div /> </OptionalChildren>
234+
</div>
235+
```
236+
237+
**Component that doesn't allow children at all:**
238+
239+
```res
240+
module NoChildren = {
241+
@react.component
242+
let make = () => {
243+
<div>
244+
{React.string("I don't accept any children params")}
245+
</div>
246+
}
247+
}
248+
249+
// The compiler will raise a type error here
250+
<NoChildren> <div/> </NoChildren>
251+
```
252+
253+
Children props are really tempting to be abused as a way to model hierarchies, e.g. `<List> <ListHeader/> <Item/> </List>` (`List` should only allow `Item` / `ListHeader` elements), but this kind of constraint is hard to enforce because all components end up being `React.element`, so it would require notorious runtime checking within `List` to verify that all children are in fact of type `Item` or `ListHeader`.
254+
255+
The best way to approach this kind of issue is by using props instead of children, e.g. `<List header="..." items=[{id: "...", text: "..."}]/>`. This way it's easy to type check the constraints, and also spares us many hours debugging and remembering component constraints.
256+
257+
**The best use-case for `children` is to pass down `React.element`s, no matter what kind of elements they are!**
258+
259+
260+
93261
### Type Inference
94262

95263
The ReScript type system excels at computing the actual prop types through usage. We recommend to explicitly type your props (especially for exported components) to help your coworkers understand the types. For simple cases or experimentation, it's still fine to omit them:
96264

97265

98-
```
266+
```res
99267
// Button.res
100268
101269
@react.component

0 commit comments

Comments
 (0)