diff --git a/CHANGES.md b/CHANGES.md index d9e8c08eb..7853b8f67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,8 @@ +# Unreleased + +* BREAKING, ppx: Allow passing an array of custom children to a component + without having to wrap in array literal ([@jchavarri in #748](https://github.com/reasonml/reason-react/pull/823)) + # 0.15.0 * Add `isValidElement` (@r17x in diff --git a/ppx/reason_react_ppx.ml b/ppx/reason_react_ppx.ml index 9646da10c..7dc8e54f1 100644 --- a/ppx/reason_react_ppx.ml +++ b/ppx/reason_react_ppx.ml @@ -458,7 +458,14 @@ let makePropsType ~loc namedTypeList = }; ] -let jsxExprAndChildren ~ident ~loc ~ctxt mapper ~keyProps children = +type component_type = Uppercase | Lowercase + +let jsxExprAndChildren ~component_type ~loc ~ctxt mapper ~keyProps children = + let ident = + match component_type with + | Uppercase -> Lident "React" + | Lowercase -> Lident "ReactDOM" + in let childrenExpr = Option.map (transformChildrenIfListUpper ~loc ~mapper ~ctxt) children in @@ -492,7 +499,10 @@ let jsxExprAndChildren ~ident ~loc ~ctxt mapper ~keyProps children = children *) ( Builder.pexp_ident ~loc { loc; txt = Ldot (ident, "jsxs") }, None, - Some (Binding.React.array ~loc children) ) + Some + (match component_type with + | Uppercase -> children + | Lowercase -> Binding.React.array ~loc children) ) | None, (label, key) :: _ -> ( Builder.pexp_ident ~loc { loc; txt = Ldot (ident, "jsxKeyed") }, Some (label, key), @@ -500,8 +510,8 @@ let jsxExprAndChildren ~ident ~loc ~ctxt mapper ~keyProps children = | None, [] -> (Builder.pexp_ident ~loc { loc; txt = Ldot (ident, "jsx") }, None, None) -let reactJsxExprAndChildren = jsxExprAndChildren ~ident:(Lident "React") -let reactDomJsxExprAndChildren = jsxExprAndChildren ~ident:(Lident "ReactDOM") +let reactJsxExprAndChildren = jsxExprAndChildren ~component_type:Uppercase +let reactDomJsxExprAndChildren = jsxExprAndChildren ~component_type:Lowercase (* Builds an AST node for the entire `external` definition of props *) let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList = diff --git a/ppx/test/lower.t/run.t b/ppx/test/lower.t/run.t index 0898fb7b4..808a6a190 100644 --- a/ppx/test/lower.t/run.t +++ b/ppx/test/lower.t/run.t @@ -94,7 +94,7 @@ ([@merlin.hide] ReactDOM.domProps)( ~children= examples - |> List.map(e => + |> List.map(e => { let Key = e.path; ReactDOM.jsxKeyed( ~key=Key, @@ -120,7 +120,7 @@ ), (), ); - ) + }) |> React.list, (), ), diff --git a/ppx/test/upper.t/run.t b/ppx/test/upper.t/run.t index a5ed6a84c..e3f259ba1 100644 --- a/ppx/test/upper.t/run.t +++ b/ppx/test/upper.t/run.t @@ -4,10 +4,7 @@ let upper_children_single = foo => React.jsx(Upper.make, Upper.makeProps(~children=foo, ())); let upper_children_multiple = (foo, bar) => - React.jsxs( - Upper.make, - Upper.makeProps(~children=React.array([|foo, bar|]), ()), - ); + React.jsxs(Upper.make, Upper.makeProps(~children=[|foo, bar|], ())); let upper_children = React.jsx( Page.make, diff --git a/test/React__test.re b/test/React__test.re index bfa76985e..4bb4d9158 100644 --- a/test/React__test.re +++ b/test/React__test.re @@ -21,12 +21,17 @@ module DummyComponentThatMapsChildren = { [@react.component] let make = (~children, ()) => {
- {children->React.Children.mapWithIndex((element, index) => { - React.cloneElement( - element, - {"key": string_of_int(index), "data-index": index}, - ) - })} + {children + ->React.array + ->React.Children.mapWithIndex((element, index) => { + React.cloneElement( + element, + { + "key": string_of_int(index), + "data-index": index, + }, + ) + })}
; }; }; @@ -318,7 +323,10 @@ describe("React", () => { act(() => { ReactDOM.Client.render( root, - render({name: "Joe", imageUrl: "https://foo.png"}), + render({ + name: "Joe", + imageUrl: "https://foo.png", + }), ) }); @@ -409,4 +417,30 @@ describe("React", () => { expect(Memo.renders^)->toBe(2); }); + + test("Can define components with custom children", () => { + let container = getContainer(container); + let root = ReactDOM.Client.createRoot(container); + + module Test = { + type t = {name: string}; + [@react.component] + let make = (~children) => { + Array.map(children, c => +
{React.string(c.name)}
+ ) + ->React.array; + }; + }; + + act(() => { + ReactDOM.Client.render( + root, + {Test.name: "foo"} {name: "bar"} , + ) + }); + + expect(container->DOM.findBySelector("div[name='foo']")->Option.isSome) + ->toBe(true); + }); });