Shmew / Feliz.MaterialUI

Feliz-style Fable bindings for Material-UI
https://shmew.github.io/Feliz.MaterialUI/
MIT License
69 stars 20 forks source link

Problem setting component' property to a ReactElementType #67

Open inouiw opened 3 years ago

inouiw commented 3 years ago

The component' property accepts a string or a ReactElementType.

I want to set the component' property of tableContainer to Mui.paper. The code below compiles but then I the following error in the console

Seq.js:34 Uncaught TypeError: o[Symbol.iterator] is not a function
    at getEnumerator (Seq.js:34)
    at fromFlatEntries (Flatten.fs.js:33)
    at MuiHelpers_createElement (Mui.fs.js:6)
    at Helptext.fs.js:13
    at renderWithHooks (react-dom.development.js:14985)
    at mountIndeterminateComponent (react-dom.development.js:17811)
    at beginWork (react-dom.development.js:19049)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3945)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3994)
    at invokeGuardedCallback (react-dom.development.js:4056)

The generated JavaScript is:

export const helptextElement2 = MuiHelpers_createElement(TableContainer, [["component", arg00 => MuiHelpers_createElement(Paper, arg00)], ["children", reactApi.Children.toArray([MuiHelpers_createElement(Table, [["size", "small"], ["children", reactApi.Children.toArray([MuiHelpers_createElement(TableHead, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableRow, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableCell, [["children", "col 1"]]), MuiHelpers_createElement(TableCell, [["children", "col 2"]])])]])])]]), MuiHelpers_createElement(TableBody, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableRow, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableCell, [["children", "lala"]]), MuiHelpers_createElement(TableCell, [["children", "liauh"]])])]])])]])])]])])]]);

The F# code is:

open Feliz
open Feliz.MaterialUI

// https://material-ui.com/components/tables/#basic-table
let helptextElement2 =
  Mui.tableContainer [
    tableContainer.component' (Fable.React.ReactElementType.ofFunction Mui.paper)
    tableContainer.children [
      Mui.table [
        table.size.small
        table.children [
          Mui.tableHead [
            Mui.tableRow [
              Mui.tableCell "col 1"
              Mui.tableCell "col 2"
            ]
          ]
          Mui.tableBody [
            Mui.tableRow [
              Mui.tableCell "lala"
              Mui.tableCell "liauh"
            ]
          ]
        ]
      ]
    ]
  ]

It would be nice if somebody could help me with the problem.

cmeeren commented 3 years ago

I'm always confused where ReactElementType is involved. @Zaid-Ajaj, do you know how this works?

inouiw commented 3 years ago

Hi,

I got a working solution. It requires creating a function. Since I did not know what type the argument should be, I inserted a console.log in the function to see what was passed and then created the IPaperProps type. A solution without needing to create a function would be nice although it is not urgent for me.

Edit: removed [<ReactComponent>] because just a function is needed.

open Feliz
open HtmlEx
open Feliz.MaterialUI

type IPaperProps =
  abstract member className: string
  abstract member children: ReactElement []

let PaperComponent (props: IPaperProps) =
  Mui.paper props.children

// https://material-ui.com/components/tables/#basic-table
let helptextElement2 =
  Mui.tableContainer [
    tableContainer.component' (Fable.React.ReactElementType.ofFunction PaperComponent)
    tableContainer.children [
      Mui.table [
        table.size.small
        table.children [
          Mui.tableHead [
            Mui.tableRow [
              Mui.tableCell "col 1"
              Mui.tableCell "col 2"
            ]
          ]
          Mui.tableBody [
            Mui.tableRow [
              Mui.tableCell "lala"
              Mui.tableCell "liauh"
            ]
          ]
        ]
      ]
    ]
  ]
cmeeren commented 3 years ago

Yes, that seems like an ugly and brittle workaround. I think there are better ways, but I'm not sure how. Perhaps @Zaid-Ajaj or other Fable/React wizards know this? 🧙‍♂️

Shmew commented 3 years ago

So I believe this would work: tableContainer.component' (importDefault "@material-ui/core/Paper").

The library could be adjusted to have something like this:

type MuiTypes =
    static member inline paper : ReactElementType = importDefault "@material-ui/core/Paper"
inouiw commented 3 years ago

@Shmew thanks. That works, and using MuiTypes it looks nice. Maybe also consider adding a method MuiTypes.fromReactElement (elem: ReactElement) and/or ReactElementType.fromReactElement (elem: ReactElement)

Shmew commented 3 years ago

Does that work for you for any component you define? I haven't personally tried it, I don't think that will work with any imported components (which is why we need the MuiTypes here).

inouiw commented 3 years ago

@Shmew sorry I am not sure if I understand your question.

By the way the version with MuiTypes compiles to

MuiHelpers_createElement(TableContainer, [["component", Paper], ["children", reactApi.Children.toArray([MuiHelpers_createElement(Table, [["size", "small"], ["children", reactApi.Children.toArray([MuiHelpers_createElement(TableHead, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableRow, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableCell, [["children", "Command"]]), MuiHelpers_createElement(TableCell, [["children", "Keybinding"]]), MuiHelpers_createElement(TableCell, [["children", "When"]])])]])])]]), MuiHelpers_createElement(TableBody, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableRow, [["children", reactApi.Children.toArray([MuiHelpers_createElement(TableCell, [["children", reactApi.Children.toArray([(() => {

Paper is declared in Paper.d.ts as export default function Paper(props: PaperProps): JSX.Element;

I like the MuiTypes approach. Though I am thinking if it could be more easily discoverable. The most intuitive would be if I could just write

tableContainer.component' Mui.Paper

Instead of

tableContainer.component' MuiTypes.Paper

But that would mean that for every "component'" method there needs to be an additional overload that does some conversion.

The following alternatives may also be worth to consider: tableContainer.component' ReactElementType.Paper tableContainer.component' MuiTypes.Paper.ElementType

Shmew commented 3 years ago

Yeah the issue is Mui.paper won't work because it's expecting a list of properties, not the actual object. I was asking if you had a component like React.functionComponent(fun props -> Html.div []) does it work for tableContainer.component'.

cmeeren commented 3 years ago

Adding a MuiTypes module as described by @Shmew in https://github.com/cmeeren/Feliz.MaterialUI/issues/67#issuecomment-766088269 seems simple, but I'm still fuzzy enough about ReactElementType and associated APIs that I'm unsure whether this is the best solution. I don't have better options, I'd just like someone knowledgeable about these matters to reassure me (or provide a better alternative).

Will probably do this at some point if no-one picks up this torch, but it'll be done sooner if someone can help me out with these clarifications.