fable-compiler / fable-react

Fable bindings and helpers for React and React Native
MIT License
273 stars 67 forks source link

voidEl breaks with: $ELEMENT is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`. #243

Open kentcb opened 1 month ago

kentcb commented 1 month ago

We recently upgraded from Fable.React 5.4.0 to 9.4.0, a rather large jump to be sure. An issue we now have is that rendering an element using either voidEl (either directly or indirectly via Fable.React.Standard) results in an error:

Uncaught Error: br is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.

Curiously, if I copy voidEl into my component and use that copy then it works. Digging a little deeper, I can see that it comes down to what JS is generated for each case. The working case generates:

react.createElement("br", {})

And the non-working case:

react.createElement("br", {}, [])

I assume the trailing empty array is triggering the error diagnostic because it is being interpreted as an attempt to assign zero children to the br, as opposed to not even attempting to assign any children.

I've got a feeling it has something to do with the ParamListAttribute on the children parameter to createElement, but not entirely sure why it manifests in one scenario and not the other.

MangelMaxime commented 1 month ago

Hello @kentcb,

Which version of Fable are you using ?

I was trying to look into the generated code using Fable REPL and reproduction code and it doesn't seems to add the empty array when it is empty.

open Fable.Core

type ReactElement = obj

type IReactExports =
    /// Create and return a new React element of the given type. The type argument can be either a tag name string (such as 'div' or 'span'), a React component type (a class or a function), or a React fragment type.
    abstract createElement: comp: obj * props: obj * [<ParamList>] children: ReactElement seq -> ReactElement
    // abstract createElement: comp: obj * props: obj -> ReactElement

let react : IReactExports = unbox null

let inline voidEl (tag: string) (props: obj seq) : ReactElement =
    react.createElement(tag, props, [])

voidEl "br" [] |> ignore

generates

import { defaultOf } from "fable-library-js/Util.js";

export const react = defaultOf();

react.createElement("br", []);
kentcb commented 1 month ago

Hi @MangelMaxime, this was with Fable 4.18.0 which I see is still latest. I think the problem with your repro is that you've had to define voidEl yourself rather than use it indirectly through Fable.React. As I mentioned, when I defined my own copy of voidEl it worked fine - it's only when using it through Fable.React that I can reproduce the problem, as though the compiler is getting confused by the extra layer of indirection.

MangelMaxime commented 1 month ago

Oh sorry, I misunderstood or was not focused enough.

Then, I suppose the issue is more with Fable compiler itself than this library.

I opened an issue to track it there: https://github.com/fable-compiler/Fable/issues/3834