fable-compiler / Fable

F# to JavaScript, TypeScript, Python, Rust and Dart Compiler
http://fable.io/
MIT License
2.89k stars 295 forks source link

Is there a way to use JSX in JSX? #3319

Open inouiw opened 1 year ago

inouiw commented 1 year ago

Description

When using the new JSX syntax, there seems to be a need to use JSX within JSX. Is it possible to use JSX in JSX?

Repro code

The following gives a compiler error because a triple quote string is used within another triple quote string.

Simplified example

[<JSX.Component>]       
let JsxInJsx () =
    JSX.jsx
        $"""
        <div>
          {
            [for x in 0..2 ->
              toReact <| JSX.jsx
                $""" // <- error here
                <div>{x}</div>
                """]
          }
        </div>
        """

The original code where I had the error

[<JSX.Component>]  
let TabBarElement model dispatch =
  $"""
    import {{ Paper, Tabs, Tab }} from "@mui/material"
    import {{ TabContext }} from '@mui/lab'

    <TabContext value={(string model.SelectedTabId)}>
        <Paper style={[style.flexGrow 1; style.marginTop 5]}>
            <div style={[style.display.flex]}>
              <Tabs value={model.SelectedTabId} variant="scrollable" onChange={(TabChanged >> dispatch)} indicatorColor="primary" textColor="primary" classes={ {|scrollButtons="MuiButton-root"|} } sx={{{{ minWidth: "0 !important" }}}}>
                {
                  model.TabItems
                  |> List.mapi (fun tabIndex t ->
                  toReact <| (JSX.jsx
                    """
                    <Tab label="test">
                      // todo
                    </Tab>
                    """)
                  )
                }
              </Tabs>
            </div>
        </Paper>
    </TabContext>
  """

Expected and actual results

Expected behaviour: It would be nice if I could nest markup and code as often as I like.

Related information

inouiw commented 1 year ago

Hm, if the F# compiler would support triple-quote interpolation strings within the expression of another triple-quote interpolation string then it should just work. Should I create an issue in the https://github.com/dotnet/fsharp repository? Edit: Created an issue: https://github.com/dotnet/fsharp/issues/14535

inouiw commented 1 year ago

When using non triple-quoted strings inside a triple-quote interpolation string, then it compiles and renders. However there is no syntax highlight for the non triple-quoted string. So I am not really happy.

[<JSX.Component>]       
let JsxInJsx () =
    JSX.jsx
        $"""
        <div>
          {
            [for x in 0..2 ->
              toReact <| JSX.jsx
                $"
                <div>{x}</div>
                "]
          }
        </div>
        """
inouiw commented 1 year ago

Since it is currently not possible to use JSX in JSX, I tried rewriting everything so that the JSX is in a separate file as described in the Feliz docs here https://zaid-ajaj.github.io/Feliz/#/Feliz/UsingJsx. However then I cannot use F# code for the onClick event handler and for other event handlers. I want to have the event handler code together with the other markup property values.

Now I am stuck. Before trying the new JSX syntax, I used the Feliz.MaterialUI library but it is not maintained any more, the new JSX syntax initially looked like a solution because it looked as if I could use the JSX directly, I was also happy to have less dependencies, however as described it is not working for me. I like F# most of all languages. It is amazing to be able to write F# and get JavaScript that works. I prefer F# and Fable to Dart and Flutter (for what I am doing in the xedit.app) because I want to be able to use all browser apis and JavaScript libraries. How great it would be if it would just work.

ncave commented 1 year ago

@inouiw Is it possible to just not nest the interpolated strings?

[<JSX.Component>]       
let JsxDiv x =
    JSX.jsx
        $"""
        <div>{x}</div>
        """

[<JSX.Component>]       
let JsxInJsx () =
    JSX.jsx
        $"""
        <div>
          {
            [ for x in 0..2 ->
                toReact (JsxDiv x) ]
          }
        </div>
        """
inouiw commented 1 year ago

@ncave thank you for the suggestion. It looks like a good temporary workaround. However it would be better if related code is defined together. The intuitive thing is to create the markup for the children inside. Here one (JavaScript) example from Github where a list of users is defined within some outer markup: https://github.com/adrianhajdin/project_chat_application/blob/master/client/src/components/TextContainer/TextContainer.js Using a separate component for the user markup would be less readable because then you could not read the code from top to bottom.

ncave commented 1 year ago

@inouiw You could also argue that it's much simpler to reason about it when you break it into smaller components (i.e. functional decomposition), but everybody has their own preference on the granularity.

My point was mainly, although I don't know much about JSX components, but I assume you also need the JSX attribute there as well for it to work. So the inner component will have to be extracted anyway as a (local) function with an attribute. And if you do that, you might as well move it outside. I may be wrong though, so I'll let @alfonsogarciacaro chime in.

I'm not suggesting that what you say is incorrect in any way (as coding style is always subjective), I'm just saying it might take some time for that F# .NET string interpolation issue to be resolved, even if it's accepted, so if you need to make progress you may have to use workarounds.