Fork me on GitHub
#reagent
<
2020-08-23
>
Yuta Sakurai18:08:24

Hi there. I'm faced with a problem using reagent + shadow-cljs + foreign React package. Some package provides additional JSX tags, and I can't find to use in hiccup form. Example: react-three-fiber package provides some JSX tags. e.g. mesh, boxBufferGeometory and so on. I want to use them as below:

clojure
(ns my.package
  (:require
    [reagent.core :as rc]
    ["react" :refer [useRef]]
    ["react-three-fiber" :refer [Canvas]]))

(defn my-component
  []
  (let [mesh-ref (useRef)]
    (rc/as-element
      [:> Canvas
        [mesh
          [boxBufferGeometory {:attach "geometory"
                               :args [1 1 1]}]]])))
But reagent can't recognize mesh tag. Anyone have solutions?

thheller19:08:40

@sakurai.yuta use (:require ["react-three-fiber" :as three]) and then [:> three/Canvas [:> three/mesh ...]] and so on. they are all in the package so you either use the namespace alias or :refer them

Yuta Sakurai19:08:24

Got these errors:

Uncaught Error: Assert failed: Invalid Hiccup form: [nil {:attach "geometry", :args [1 1 1]}]
boxBufferGeometory defined in react-three-fiber/src/three-types.ts. Referenced code is below:
declare global {
  namespace JSX {
    interface IntrinsicElements {
...omit...
      mesh: ReactThreeFiber.Object3DNode<THREE.Mesh, typeof THREE.Mesh>
...omit...
      boxBufferGeometry: ReactThreeFiber.BufferGeometryNode<THREE.BoxBufferGeometry, typeof THREE.BoxBufferGeometry>
...omit...
}}}
In react-three-fiber JS examples, seems no need to import/require mesh , boxBufferGeometory and so on. Maybe these are not Javascript Class, sugar-syntax for JSX template.

thheller19:08:33

hmm I guess it might want [:mesh .. [:boxBufferGeometry ]] then?

thheller19:08:22

not sure if reagent maybe only allows :div and such

arttuka19:08:16

apparently react-three-fiber does some jsx magic that converts into three.js expressions: > <mesh /> simply is another expression for `new THREE.Mesh()`

thheller19:08:25

there is no mention of this in the docs I can find?

thheller19:08:39

it makes sense that [:mesh ..] should work given that the JSX rules are to treat all lower-case tags as regular html tags. eg <div ...> becomes React.createElement("div", ...)

thheller19:08:56

ah. I think just comparing to what is going to happen under the hood at runtime. not what the actual JSX produces.

thheller19:08:12

JSX can't express :div vs Component (keyword vs symbol) so I think their logic is lower-case = html tag, upper case = component

Yuta Sakurai19:08:10

Umm... I tried first the code:

[:mesh ... [:boxBufferGeometry ...]]]
and got errors:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
@U3T4X9F8X Thank you for reply. Thats what I thought I should to use raw THREE.Mesh() . Maybe the method works well. I think reason why is reagent find the mesh and can't recognize the tag is additional JSX tag. Basically React component is impletented as Javascript Class, so we can import and use in hiccup forms, but react-three-fiber don't use this approach.

Yuta Sakurai19:08:05

I will try using raw THREE.Mesh() instead of mesh JSX tag. Thank you all!

thheller20:08:10

are you sure you didn't have any leftover [mesh ...] or so? :mesh definitely doesn't leave behind a undefined while mesh would

thheller20:08:33

(and you also should be getting a compiler warning for)

Yuta Sakurai20:08:26

Tried code:

(let [mesh-ref (useRef)]
    (rc/as-element
      [Canvas
       [mesh {:ref mesh-ref
              :scale [1 1 1]}
        [boxBufferGeometry {:attach "geometry"
                            :args [1 1 1]}]
        [meshStandardMaterial {:attach "material"
                               :color "hotpink"}]]])))
And got compiler warnings:
------ WARNING #1 - :undeclared-var --------------------------------------------
   File: /home/***/src/main/myns/code.cljs:16:9
  --------------------------------------------------------------------------------
    13 |   (let [mesh-ref (useRef)]
    14 |     (rc/as-element
    15 |       [Canvas
    16 |        [mesh {:ref mesh-ref
  ---------------^----------------------------------------------------------------
   Use of undeclared Var myns/mesh
  --------------------------------------------------------------------------------
    17 |               :scale [1 1 1]}
    18 |         [boxBufferGeometry {:attach "geometry"
    19 |                             :args [1 1 1]}]
    20 |         [meshStandardMaterial {:attach "material"
  --------------------------------------------------------------------------------
and same 2 warnings(`boxBufferGeometory`, meshStandardMaterial). Got the error when executed:
Uncaught Error: Assert failed: Invalid Hiccup form: [#js {"$$typeof" #object[Symbol(react.memo)], :type #object[Function], :compare nil} [nil {:ref #js {:current nil}, :scale [1 1 1]} [nil {:attach "geometry", :args [1 1 1]}] [nil {:attach "material", :color "hotpink"}]]]

thheller20:08:20

Canvas is an actual component so that is a symbol

thheller20:08:32

mesh is a not a component so should be :mesh

thheller20:08:09

this Assert failed: Invalid Hiccup form is completely expected after Use of undeclared Var myns/mesh

thheller20:08:41

(let [mesh-ref (useRef)]
  (rc/as-element
    [:> Canvas
     [:mesh {:ref mesh-ref
             :scale [1 1 1]}
      [:boxBufferGeometry {:attach "geometry"
                           :args [1 1 1]}]
      [:meshStandardMaterial {:attach "material"
                              :color "hotpink"}]]])) )

thheller20:08:46

this should be fine

thheller20:08:51

although I'm not too sure about :scale and :args. maybe that needs to be :scale #js [1 1 1]

Yuta Sakurai20:08:12

Finally, the code:

(defn fn$animation-lantern
  []
  (let [mesh-ref (useRef)]
    (rc/as-element
      [:> Canvas
       [:mesh {:ref mesh-ref
              :scale [1 1 1]}
        [:boxBufferGeometry {:attach "geometry"
                            :args [1 1 1]}]
        [:meshStandardMaterial {:attach "material"
                               :color "hotpink"}]]])))
Got error:
Uncaught TypeError: lastCallbackNode is not a function
    at flushFirstCallback (scheduler.development.js:108)
    at flushImmediateWork (scheduler.development.js:170)
    at Object.exports.unstable_runWithPriority (scheduler.development.js:262)
    at completeRoot (react-dom.development.js:20418)
    at performWorkOnRoot (react-dom.development.js:20347)
    at performWork (react-dom.development.js:20255)
    at requestWork (react-dom.development.js:20229)
    at scheduleWork (react-dom.development.js:19912)
    at dispatchAction (react-dom.development.js:13600)
    at ResizeObserver.callback (web.cjs.js:71)

Yuta Sakurai20:08:21

Okey, args must be js array, so fixed to (clj->js [1 1 1]) and no changes.

thheller20:08:24

why is this react-dom? aren't you supposed to use something from react-three-fiber to do the rendering?

thheller20:08:17

or maybe thats what Canvas is for? I'm really just guessing here 😛

Yuta Sakurai20:08:23

react-three-fiber is React renderer, so create args for ReactDom.render() . Plain Javascript example:

ReactDOM.render(
  <Canvas>
    <ambientLight />
    <pointLight position={[10, 10, 10]} />
    <Box position={[-1.2, 0, 0]} />
    <Box position={[1.2, 0, 0]} />
  </Canvas>,
  document.getElementById('root')
)
Canvas is React component implemented by react-three-fiber to render component to create Three.js render area(maybe).

thheller20:08:43

hmm ok. then I have no clue what the above error is about. I have seen it before but no clue what the cause was.

Yuta Sakurai20:08:47

Ah, maybe the problem is just from Canvas, not JSX tag(`mesh` and so on)...?

thheller20:08:13

oh wait ... you are using useRef which means this must be a function component

thheller20:08:33

are you calling that correctly?

thheller20:08:57

where do you use fn$animation-lantern? would help to see the full code instead of guessing 😉

Yuta Sakurai20:08:34

These are full codes, not masked any names. Pretend not to see, please. src/main/tsuguten/component/animation_lantern.cljs:

(ns tsuguten.component.animation-lantern
  (:require
    ["react" :refer [useRef useState]]
    ["react-three-fiber" :refer [Canvas useFrame] :as rtf]
    [reagent.core :as rc]
    ["three" :refer [Mesh
                     MeshStandardMaterial
                     BoxBufferGeometry]]))


(defn fn$animation-lantern
  []
  (let [mesh-ref (useRef)]
    (rc/as-element
      [:> Canvas
       ])))


(defn $animation-lantern
  []
  (fn []
    [:> fn$animation-lantern]))
and required by src/main/tsuguten/component/timeline.cljs:
(ns tsuguten.component.timeline
  (:require
    [com.rpl.specter :as sp
     :refer-macros [transform setval select]]
    [re-frame.core
     :refer [dispatch subscribe reg-sub reg-event-fx]]
    [reagent.core :as rc]
    [tsuguten.component.animation-lantern
     :refer [$animation-lantern]]
    [tsuguten.component.floating-lantern
     :refer [$floating-lantern]]
    [tsuguten.component.timeline.message
     :refer [$message]]
    [tsuguten.util.api :as ua]
    [tsuguten.util.component :as uc]
    [tsuguten.util.nav :as un]))


(defn fn$timeline
  []
  (let [hiccup (uc/get-component :timeline)]
    (rc/as-element
      (->> hiccup
           (setval [(un/BY-ID "animation-lantern")
                    un/TAG]
                   $animation-lantern)
           (setval [(un/BY-ID "floating-lantern")
                    un/TAG]
                   $floating-lantern)
           (setval [(un/BY-ID "timeline-message")
                    un/TAG]
                   $message)))))


(defn $timeline
  []
  (fn []
    (let [_ @(subscribe [::uc/component :timeline])]
      [:> fn$timeline])))
and required by(top level) src/main/tsuguten/core.cljs:
(ns tsuguten.core
  (:require
    [clojure.browser.dom :as dom]
    [re-frame.core
     :refer [dispatch-sync dispatch subscribe
             reg-sub]]
    ["react-modal" :as Modal]
    ["react-router-dom" :refer [Switch Route
                                useHistory]
     :rename {BrowserRouter Router}]
    [reagent.core :as rc]
    [tsuguten.component.ask-load :refer [$ask-load]]
    [tsuguten.component.confirm :refer [$confirm]]
    [tsuguten.component.intro :refer [$intro]]
    [tsuguten.component.message :refer [$message]]
    [tsuguten.component.profile :refer [$profile]]
    [tsuguten.component.select-actions :refer [$select-actions]]
    [tsuguten.component.select-hometown :refer [$select-hometown]]
    [tsuguten.component.select-lantern :refer [$select-lantern]]
    [tsuguten.component.termsofuse :refer [$termsofuse]]
    [tsuguten.component.throw :refer [$throw]]


(defn on-load
  []
  (dispatch-sync [::events/initialize-db])
  (when (:sound? @re-frame.db/app-db)
    (.. (dom/get-element "audio")
        play))
  (.setAppElement Modal "#root")
  (rc/render-component
    [$root]
    (dom/get-element "root")))


(defn init
  []
  (js/console.info "tsuguten.core/init")
  (on-load))


(defn ^:dev/before-load before-load
  []
  (js/console.info "tsuguten.core/before-load")
  (enable-console-print!))


(defn ^:dev/after-load after-load
  []
  (js/console.info "tsuguten.core/after-load")
  (on-load))

thheller20:08:49

hmm yeah that needs to be a function component but isn't. I don't know what the current state of reagent is on that front

thheller20:08:27

ah no wait you are calling it as a function. sorry this goes way beyond my reagent knowledge. I think the three/jsx parts are correct now. no clue about the other though

Yuta Sakurai20:08:25

Hmm... other codes are using useHistory as same code, so seems no problems how to use useRef . Yeah, I failed that I thought the problem is made by JSX blindly. The full code is not including mesh JSX tag and occurs same error(`lastCallbackNode is not a function`). I need more research. Thanks all for supporting, I will report solutions if find it.

thheller21:08:12

knew I saw this error before. maybe that helps tracking it down? https://github.com/reagent-project/reagent/issues/502

Yuta Sakurai21:08:53

Thank you, I just find the issue now. Now trying to read carefully. (maybe already you know, my English skill is very poor)

Yuta Sakurai12:08:46

Finally I tried to them below and works fine. • upgrade reagent from "0.8.1" to "0.10.0" • Latest code:

(ns tsuguten.component.animation-lantern
  (:require
    ["react" :refer [useRef useState]]
    ["react-three-fiber" :refer [Canvas useFrame] :as rtf]
    [reagent.core :as rc]
    ["three" :refer [Mesh
                     MeshStandardMaterial
                     BoxBufferGeometry]]))


(defn fn$animation-lantern
  []
  (let [mesh-ref (useRef)]
    (rc/as-element
      [:> Canvas
       [:ambientLight]
       [:pointLight {:position (clj->js [10 10 10])}]
       [:mesh {:ref mesh-ref
               :scale (clj->js [1 1 1])}
        [:boxBufferGeometry {:attach "geometry"
                             :args (clj->js [1 1 1])}]
        [:meshStandardMaterial {:attach "material"
                                :color "hotpink"}]]])))


(defn $animation-lantern
  []
  (fn []
    [:> fn$animation-lantern]))
$animation-lantern can use as an element of hiccup form like this: [$animation-lantern] This code works fine. I hope this will help.

thheller19:08:26

just like in JS the names need to come from somewhere. so the JS examples likely also just import or require them?

Yuta Sakurai19:08:44

Thanks for just quickly reply, @thheller. Now trying your suggest, plz wait... // I really appreciate what you've developped shadow-cljs.