Skip to content

Commit 6475d40

Browse files
committed
fix(js/ts): add support for match clauses in JSX.create
1 parent 9d97547 commit 6475d40

5 files changed

Lines changed: 127 additions & 0 deletions

File tree

src/Fable.Transforms/Fable2Babel.fs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2142,6 +2142,13 @@ but thanks to the optimisation done below we get
21422142
21432143
***)
21442144

2145+
// A sequence lambda can only be flattened away when its body is a single `return`
2146+
// (handled by the ArrowFunctionExpression cases of the Unroller below).
2147+
let isFlattenableSeqLambdaBody (body: Statement array) =
2148+
match body with
2149+
| [| ReturnStatement _ |] -> true
2150+
| _ -> false
2151+
21452152
// Check if the provided expression is equal to the expected identiferText (as a string)
21462153
let rec (|IdentifierIs|_|) (identifierText: string) expression =
21472154
match expression with
@@ -2171,6 +2178,16 @@ but thanks to the optimisation done below we get
21712178
| expr :: Unroller rest ->
21722179
match expr with
21732180
| CalledExpression "toList" exprs -> exprs @ rest
2181+
// A `delay(fun () -> ...)` whose lambda body cannot be flattened (e.g. a `match`
2182+
// arm that binds a value before a `for` loop) must not be stripped down to a bare
2183+
// lambda, which React rejects ("Functions are not valid as a React child"). The
2184+
// `delay` only adds laziness, which is pointless for a JSX child, so invoke the
2185+
// lambda directly and let React render the produced sequence.
2186+
| CallExpression(IdentifierIs "delay",
2187+
[| (ArrowFunctionExpression([||], BlockStatement body, _, _, _)) as lambda |],
2188+
_,
2189+
_) when not (isFlattenableSeqLambdaBody body) ->
2190+
CallExpression(lambda, [||], [||], None) :: rest
21742191
| CalledExpression "delay" exprs -> exprs @ rest
21752192
| ArrowFunctionExpression([||],
21762193
BlockStatement [| ReturnStatement(ArrayExpression(UnrollerFromArray exprs, _),

tests/Integration/Integration/data/jsxListOptimisation/Components.fs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,33 @@ let propsCanUseUnbox =
163163
unbox<JSX_IReactProperty> ("id", "myid")
164164
]
165165

166+
let divWithMatchContainingForLoop (counter: int) =
167+
Html.div [
168+
match counter with
169+
| 0 -> Html.div "No items!"
170+
| items ->
171+
let total = items + 1
172+
for i in 1..total do
173+
Html.div [
174+
prop.key i
175+
prop.text (i + total)
176+
]
177+
]
178+
179+
let divWithElementBeforeMatchContainingForLoop (counter: int) =
180+
Html.div [
181+
Html.div "Header"
182+
match counter with
183+
| 0 -> Html.div "No items!"
184+
| items ->
185+
let total = items + 1
186+
for i in 1..total do
187+
Html.div [
188+
prop.key i
189+
prop.text (i + total)
190+
]
191+
]
192+
166193
// Regression test for https://github.com/fable-compiler/Fable/issues/3839
167194
// String values longer than ~100 chars were wrapped in a Let binding by Fable,
168195
// causing transformJsxProps to fail with "Cannot detect JSX prop key at compile time".

tests/Integration/Integration/data/jsxListOptimisation/Components.jsx.expected

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,44 @@ export function divWithConditionalWithoutElseBranchWorks(show) {
111111

112112
export const propsCanUseUnbox = <div id="myid" />;
113113

114+
export function divWithMatchContainingForLoop(counter) {
115+
return <div>
116+
{(() => {
117+
if (counter === 0) {
118+
return singleton(<div>
119+
No items!
120+
</div>);
121+
}
122+
else {
123+
const total = (counter + 1) | 0;
124+
return map((i) => <div key={int32ToString(i)}>
125+
{i + total}
126+
</div>, rangeDouble(1, 1, total));
127+
}
128+
})()}
129+
</div>;
130+
}
131+
132+
export function divWithElementBeforeMatchContainingForLoop(counter) {
133+
return <div>
134+
<div>
135+
Header
136+
</div>
137+
{(() => {
138+
const matchValue = counter | 0;
139+
if (matchValue === 0) {
140+
return singleton(<div>
141+
No items!
142+
</div>);
143+
}
144+
else {
145+
const total = (matchValue + 1) | 0;
146+
return map((i) => <div key={int32ToString(i)}>
147+
{i + total}
148+
</div>, rangeDouble(1, 1, total));
149+
}
150+
})()}
151+
</div>;
152+
}
153+
114154
export const divWithLongClassName = <div className="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" />;

tests/React/__tests__/JSX_API.fs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ Jest.describe("JSX API tests (using React)", fun () ->
6666
matchSnapshot Components.divWithForLoop
6767
)
6868

69+
Jest.test("Element can have a match arm that binds a value before a for loop", fun () ->
70+
matchSnapshot (Components.divWithMatchContainingForLoop 2)
71+
)
72+
73+
Jest.test("Element can have a sibling before a match arm that binds a value before a for loop", fun () ->
74+
matchSnapshot (Components.divWithElementBeforeMatchContainingForLoop 2)
75+
)
76+
6977
Jest.test("Fragments are supported", fun () ->
7078
matchSnapshot Components.divWithFragment
7179
)

tests/React/__tests__/__snapshots__/JSX_API.fs.js.snap

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
22

3+
exports[`JSX API tests (using React) Element can have a match arm that binds a value before a for loop 1`] = `
4+
<div>
5+
<div>
6+
<div>
7+
4
8+
</div>
9+
<div>
10+
5
11+
</div>
12+
<div>
13+
6
14+
</div>
15+
</div>
16+
</div>
17+
`;
18+
19+
exports[`JSX API tests (using React) Element can have a sibling before a match arm that binds a value before a for loop 1`] = `
20+
<div>
21+
<div>
22+
<div>
23+
Header
24+
</div>
25+
<div>
26+
4
27+
</div>
28+
<div>
29+
5
30+
</div>
31+
<div>
32+
6
33+
</div>
34+
</div>
35+
</div>
36+
`;
37+
338
exports[`JSX API tests (using React) Element can have for loops 1`] = `
439
<div>
540
<div>

0 commit comments

Comments
 (0)