Difference in handling of children in JSX2 and JSX3 components


#1

The documentation surrounding children with the new JSX3 approach seems to be a bit broken.

It claims that <MyReasonComponent> <div /> <div /> </MyReasonComponent> would pass an array of two elements to MyReasonComponent, and that <MyReasonComponent> <div /> </MyReasonComponent> would pass a single element to MyReasonComponent.

However, my tests indicate that it will always send a single element - never an array. This seems to be the opposite behavior in JSX2, where an array of react elements is always passed, regardless of whether one or many elements are present as children.

Here’s a repo that demonstrates how children work right now between JSX2 and JSX3: https://github.com/harigopal/jsx3-children-issue-demo/

And here’s the JSX3 component that accepts children, with a JSX2 wrapper module:

[@bs.config {jsx: 3}];

[@react.component]
let make = (~message, ~children) =>
  <div>
    <div>
      {
        "This is a message sent to AcceptsChildren: " ++ message |> React.string
      }
    </div>
    children
  </div>;

module Jsx2 = {
  let component = ReasonReact.statelessComponent("AcceptsChildren");

  let make = (~message, children) =>
    ReasonReactCompat.wrapReactForReasonReact(
      make,
      makeProps(~message, ~children=children |> React.array, ()),
      children,
    );
};

The important bit here is the inclusion of children in the call to makeProps in the Jsx2 module, in addition to passing it to wrapReactForReactReact - this seems to be undocumented.

Also note that children from JSX2 must be converted to a React.element (using React.array above) before it is accepted by JSX3, which looks like unintended behaviour.

Does anyone know if this is the expected behaviour? Am I misreading the documentation in some way? My team had to spend a few hours to figure out how to get this working.


#2

Hey @harigopal,

You are 100% right in that the behavior of the JSX ppx and the way children are handled have changed significantly from JSX2 to JSX3.

JSX2 used to “wrap” children with an array in all cases, except when using the spread operator.

JSX3 works a little bit differently:

Single child

When creating an element that only has a single child, like

<Test> one </Test>

the child will be passed as is to the component, similar to what happened in JSX2 with the spread operator.

Multiple children

When there are multiple children

<Test> one two </Test>

As you mentioned, they are not passed as an array, but as a value of type element. I agree with you that the docs are misleading in that sense (a great contribution opportunity!).

Re: backwards compatibility with JSX2 components, the solution for this is to use React.array to convert from array-like children coming from Jsx2 to element children needed in Jsx3. I have started documenting some ideas about this. We’re still discussing about making a whole section to discuss about ReasonReactCompat, but you can see the current work in https://github.com/reasonml/reason-react/pull/404. Any feedback you might have on how to improve these docs is welcome.