Feliz.MaterialUI icon indicating copy to clipboard operation
Feliz.MaterialUI copied to clipboard

Problem setting component' property to a ReactElementType

Open inouiw opened this issue 3 years ago • 9 comments

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.

inouiw avatar Jan 22 '21 21:01 inouiw

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

cmeeren avatar Jan 22 '21 21:01 cmeeren

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"
            ]
          ]
        ]
      ]
    ]
  ]

inouiw avatar Jan 23 '21 09:01 inouiw

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? 🧙‍♂️

cmeeren avatar Jan 23 '21 13:01 cmeeren

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"

Shmew avatar Jan 23 '21 14:01 Shmew

@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)

inouiw avatar Jan 23 '21 15:01 inouiw

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).

Shmew avatar Jan 24 '21 00:01 Shmew

@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

inouiw avatar Jan 24 '21 09:01 inouiw

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'.

Shmew avatar Jan 24 '21 18:01 Shmew

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.

cmeeren avatar Feb 05 '21 19:02 cmeeren