Repository navigation
[JSX] Prevent making the whole children list dynamic when having a for loop in a list of children #4079
Copy link
Copy link
Closed
Description
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
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
[-][JSX] Prevent make the whole children list dynamic when having a for loop in a list of children[/-][+][JSX] Prevent making the whole children list dynamic when having a for loop in a list of children[/+]MangelMaxime commentedon Mar 16, 2025
I think we will also need to wrap / convert the
toListinto a native JavaScript array astoListwill create a F# list.MangelMaxime commentedon Mar 17, 2025
After some testing it seems like we can keep
toListas is at least this works for react.The reason why it works, is probably because
FSharpListimplements theGetEnumerator/[Symbol.iterator]shayanhabibi commentedon Mar 18, 2025
I ran into this when dealing with JSX outputs as this was preventing attributes in Solid-js from working when using simple conditional statements, my fix was to just aggressively unroll the default Fable AST 😅
Since Solid-js doesn't permit loops/map in children as it is unreactive and provides the For and Index primitive components instead, I didn't have to do it inside the children blocks.
What exactly is
delay?MangelMaxime commentedon Mar 18, 2025
@shayanhabibi Thanks for the hint, I was doing something similar in Fable.
Having your implementation will probably save me some time and trouble 😇
MangelMaxime commentedon Apr 17, 2025
@shayanhabibi Thanks a lot for your initial implementation of the Unroller.
I would not have though of doing it this way, and it really helped me get a clean implementation I think.
MangelMaxime commentedon Apr 17, 2025
delayis coming from F# Computation Expressionhttps://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/computation-expressions#creating-a-new-type-of-computation-expression