Skip to content

[JSX] Prevent making the whole children list dynamic when having a for loop in a list of children #4079

@MangelMaxime

Description

@MangelMaxime

Hello,

Consider the following DSL (this is Feliz adapted to use Fable JSX module)

open Fable.Core
open Fable.Core.JsInterop

[<ImportMember("react")>]
[<AllowNullLiteral>]
type ReactElement = interface end

type private IReactProperty = JSX.Prop

[<Erase>]
type prop =
    static member inline children(children: ReactElement list) : IReactProperty = "children", children

    static member inline text(value: int) : IReactProperty = "children", [ JSX.text !!value ]

    static member inline key(value: int) : IReactProperty = "key", string value

[<Erase>]
type Html =

    static member inline fragment(children: ReactElement list) : ReactElement =
        JSX.create "" [ "children" ==> children ] |> unbox

    static member inline div(number: int) : ReactElement =
        JSX.create "div" [ "children" ==> JSX.text (!!number) ] |> unbox

    static member inline div(children: ReactElement list) : ReactElement =
        JSX.create "div" [ "children" ==> children ] |> unbox

    static member inline div(text: string) : ReactElement =
        JSX.create "div" [ "children" ==> JSX.text text ] |> unbox

    static member inline div(props: IReactProperty list) : ReactElement = JSX.create "div" props |> unbox
[<Erase; Mangle(false)>]
type Test =

    [<ExportDefault>]
    static member Test() =
        Html.div
            [
                Html.div "Test 1"
                Html.div "Test 2"
            ]
export function Test() {
    return <div>
        <div>
            Test 1
        </div>
        <div>
            Test 2
        </div>
    </div>;
}

export default Test;

which is fine as all the children are "static".

However if we do, we add a for loop, this transform the whole children list into a dynamic list.

[<Erase; Mangle(false)>]
type Test =

    [<ExportDefault>]
    static member Test() =
        Html.div
            [
                Html.div "Test 1"
                Html.div "Test 2"
                for i in 0..2 do
                    Html.div [
                        prop.key i
                        prop.text i
                    ]
            ]
export function Test() {
    return <div>
        {toList(delay(() => append(singleton(<div>
            Test 1
        </div>), delay(() => append(singleton(<div>
            Test 2
        </div>), delay(() => map((i) => <div key={int32ToString(i)}>
            {i}
        </div>, rangeDouble(0, 1, 2))))))))}
    </div>;
}

export default Test;

This is problematic because now React will complain that not all child have a key prop.

We can work around this issue by wrapping the list in React.fragment, as it scope the dynamic list to only the for loop scope.

[<Erase; Mangle(false)>]
type Test =

    [<ExportDefault>]
    static member Test() =
        Html.div
            [
                Html.div "Test 1"
                Html.div "Test 2"
                Html.fragment
                    [
                        for i in 0..2 do
                            Html.div [ 
                                prop.key i
                                prop.text i 
                            ]
                    ]
            ]
export function Test() {
    return <div>
        <div>
            Test 1
        </div>
        <div>
            Test 2
        </div>
        <>
            {toList(delay(() => map((i) => <div key={int32ToString(i)}>
                {i}
            </div>, rangeDouble(0, 1, 2))))}
        </>
    </div>;
}

export default Test;

However, this is a standard pattern in JSX and would be nice to be supported out of the box.

Candidate for the output would be something like:

export function Test() {
    return <div>
        <div>
            Test 1
        </div>
        <div>
            Test 2
        </div>
        {toList(delay(() => map((i) => <div key={int32ToString(i)}>
                {i}
            </div>, rangeDouble(1, 1, 10))))}
    </div>;
}

I think it should be doable, by detecting the toList call and extracting places where we see delay(() => append(singleton(...

Issue originally reported by @Freymaurer

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions