Zaid-Ajaj / Feliz

A fresh retake of the React API in Fable and a collection of high-quality components to build React applications in F#, optimized for happiness
https://zaid-ajaj.github.io/Feliz/
MIT License
540 stars 78 forks source link

Uncaught ReferenceError: React is not defined #486

Closed arthur-s closed 2 years ago

arthur-s commented 2 years ago

Thanks for Feliz! I have an issue with 3rd party React component integration. I've a self-written js component in ./js/Modal.js:

import { useEffect, useRef } from "react";
import { useState, useLayoutEffect } from 'react';
import { createPortal } from 'react-dom';
import { CSSTransition } from "react-transition-group";

function Modal({ children, isOpen, handleClose }) {
 // content
}
export default Modal;

I import it in F# code as:

module Messages
// imports

[<ReactComponent(import="default", from="./js/Modal.js")>]
let Modal (isOpen:bool) (handleClose: bool -> unit) (children: Feliz.ReactElement) = React.imported()

// this also didn't help:
//let Modal (isOpen:bool) (handleClose: bool -> unit) (children: Feliz.ReactElement) = 
//    let properties = ["isOpen" ==> isOpen; "handleClose" ==> handleClose; "children" ==> children]
//    Interop.reactApi.createElement(importDefault "./js/Modal.js", createObj !!properties)

It works when I include react's script into index.html:

<script src="/js/react.development.js">
<script src="/js/react-dom.development.js">

But if I don't use this scripts, I got an error: Uncaught ReferenceError: React is not defined.

How to properly import custom react component? (I don't want to port a .js component into .fs)

Zaid-Ajaj commented 2 years ago

Hi @arthur-s can you please show where this error is coming from exactly? As in the stack trace of the error because based on your information I don't see why it would result in error Uncaught ReferenceError: React is not defined

arthur-s commented 2 years ago

Hi @Zaid-Ajaj , I discovered next:

If component returns string, then it works, but if it returns JSX, then doesn't:

function Modal({ children, isOpen, handleClose }) {
    // works
    return (
        ""
    );
}

function Modal({ children, isOpen, handleClose }) {
    // doesn't work:
    return (
        <span>Hello</span>
    );
}
MangelMaxime commented 2 years ago

Hello @arthur-s, according to your last message I would lean to a wrongly configured bundler.

When using returning a string you are using standard JavaScript syntax. But when using JSX, this is not standard and needs to be transformed.

I am not sure it it will fix your problem because if it was related to JSX I would expect a different message from the compiler. Like, unexpected syntax near <span>Hello</span>

arthur-s commented 2 years ago

Yes, looks like problem is in JSX. I've renamed file to Modal.jsx, but it didn't help.

According to https://babeljs.io/docs/en/babel-plugin-transform-react-jsx, jsx code transpiles to React.createElement() if jsx runtime is classic:

In:

/** @jsxRuntime classic */

const profile = (
  <div>
    <img src="avatar.png" className="profile" />
    <h3>{[user.firstName, user.lastName].join(" ")}</h3>
  </div>
);

Out:

const profile = React.createElement(
  "div",
  null,
  React.createElement("img", { src: "avatar.png", className: "profile" }),
  React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);

If I change runtime to automatic, then all works fine:

presets: [
            "@babel/preset-env",
            ["@babel/preset-react", { "development": true, "runtime": "automatic" }]
        ]

But when I use automatic runtime, I guess that duplicated React sources include into bundle, increasing it's size.

Also, this file uses createElement instead of JSX: https://github.com/Zaid-Ajaj/Feliz/blob/master/Feliz.PigeonMaps/Marker.js#L61, it's interesting why.

arthur-s commented 2 years ago

I tested, this works:

import { createElement } from "react";

...
return (
    createElement(
            "div",
            null,
            createElement("img", { src: "avatar.png", className: "profile" }),
            createElement("h3", null, "Foo")
        )
)
arthur-s commented 2 years ago

Ok, I've fixed it using pragma option for @babel/preset-react. I use next config:

["@babel/preset-react", {
                "development": isDevelopment,
                "pragma": "createElement" }]

And in order to make it work, I also need to include createElement in js module:

import { createElement } from "react";