Docs / ReasonReact / Children

Children

The Record API is in feature-freeze. For the newest features and better support going forward, please consider migrating to the new function components.

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:

JS
// 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>):

JS
// 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:

JS
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 the children you pass to be array(reactElement). For example, <div> [1, 2, 3] </div> or <span> Some(10) </span> don't work, since the list and the option are forwarded to the underlying ReactJS div/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:

RE
/* 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:

RE
<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:

RE
<MyLayout> a b </MyLayout> <MyLayout> a </MyLayout>

These actually become:

RE
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:

    RE
    <MyForm> ...(<div />, <div />) </MyForm>
  • A component that mandates a callback, no need for ReactJS' React.Children.only runtime check:

    RE
    <Motion> ...((name) => <div className=name />) </Motion>
  • A layout helper that accepts a variant of TwoRows | ThreeRows | FourColumns

    RE
    <Layout> ...(ThreeRows(<div />, child2, child3)) </Layout>