Children
ReasonReact children are like ReactJS children, except we provide more bells and whistles that leverages the language itself.
Let's say people are using your component like so: <Animation><div /></Animation>
. In the Animation
component, you'd want to constrain the children
being passed to you (the div
here) as a single child. Aka, you'd like to error on this: <Animation><div /><div /></Animation>
.
ReactJS provides such helper:
// inside Animation.js
render: () => {
return React.Children.only(this.children);
}
Or, maybe you made a Layout component and accepts exactly two children (e.g. <Layout><MyColumn /><MyColumn /></Layout>
):
// Layout.js
render: () => {
if (React.Children.count(this.props.children) !== 2) {
// ... error
}
return (
<div>
{this.props.children[0]}
<Separator />
{this.props.children[1]}
</div>
);
}
Or maybe you mandate a children callback:
render: () => {
return React.Children.only(this.props.children('hello'));
}
As an author of these JS components, you can only hope that the user provided the right children type to you, and throw an error for them if not. In ReasonReact, children
prop is typed and prevents bad usage from the consumer. This seems pretty natural, once you realize that children
props is basically like any other prop! Yes, you can pass tuple, variant, record and others to children! This also mean we don't have to provide the equivalent React.Children.*
utils in ReasonReact at all!
However, due to syntactical and interoperability reasons, we do have some small restrictions on children
:
- DOM elements such as
div
,span
and others mandate thechildren
you pass to bearray(reactElement)
. For example,<div> [1, 2, 3] </div>
or<span> Some(10) </span>
don't work, since thelist
and theoption
are forwarded to the underlying ReactJSdiv
/span
and such data types don't make sense in JS. - User-defined elements, by default, also accept an array (of anything). This is due to a rather silly constraint of the JSX syntax, described below.
Syntax Constraint
Technically, if you've written a component that accepts a tuple as children
:
/* MyForm.re */
type tuple2Children = (ReasonReact.reactElement, ReasonReact.reactElement);
let make = (children: tuple2Children) => {
...component,
render: _self => {
<div>
{fst(children)}
<Separator />
{snd(children)}
</div>
}
};
Then you can expect the user to pass you a tuple:
<MyForm> (<div />, <div />) </MyForm>
This, however, will give you a type error:
This has type:
array('a)
But somewhere wanted:
tuple2Children (defined as (ReasonReact.reactElement, ReasonReact.reactElement))
It means that MyForm
is expecting the tuple, but you're giving array
instead! What's happening? Well, look at what JSX is transformed into:
<MyLayout> a b </MyLayout>
<MyLayout> a </MyLayout>
These actually become:
ReasonReact.element(
MyLayout.make([|a, b|])
);
ReasonReact.element(
MyLayout.make([|a|])
);
See how the second MyLayout
's children is also wrapped in an array? We can't special-case <MyLayout> a </MyLayout>
to pass a
without the array wrapping (it'd give even more confusing errors). Since children is usually an array, we've decided to always wrap it with an array, no matter how many items we visually see in the JSX. But what if you really want to pass unwrapped data?
Children Spread
Just use <MyLayout> ...a </MyLayout>
. This will simply transform into ReasonReact.element(MyLayout.make(a))
, aka without the array wrapping.
Tips & Tricks
Here are some use-cases for children spread + Reason built-in data structures:
A layout component that mandates a tuple of 2 react element and shows them side-by-side:
<MyForm> ...(<div />, <div />) </MyForm>
A component that mandates a callback, no need for ReactJS'
React.Children.only
runtime check:<Motion> ...((name) => <div className=name />) </Motion>
A layout helper that accepts a variant of
TwoRows | ThreeRows | FourColumns
<Layout> ...(ThreeRows(<div />, child2, child3)) </Layout>