reagent

A minimalistic ClojureScript interface to React.js http://reagent-project.github.io/
Yuta Sakurai 2020-08-23T18:58:24.003Z

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?

thheller 2020-08-23T19:06:40.004200Z

@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 Sakurai 2020-08-24T12:42:46.019500Z

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.

thheller 2020-08-23T19:07:26.005100Z

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

Yuta Sakurai 2020-08-23T19:13:44.007900Z

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

Yuta Sakurai 2020-08-23T19:29:37.011Z

See also: https://github.com/react-spring/react-three-fiber

thheller 2020-08-23T19:36:33.011400Z

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

thheller 2020-08-23T19:37:22.011700Z

not sure if reagent maybe only allows :div and such

arttuka 2020-08-23T19:39:16.011900Z

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

thheller 2020-08-23T19:42:25.012100Z

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

thheller 2020-08-23T19:43:39.012300Z

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

arttuka 2020-08-23T19:44:33.012500Z

the quote was from here: https://github.com/react-spring/react-three-fiber#does-it-have-limitations

thheller 2020-08-23T19:45:56.012800Z

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

thheller 2020-08-23T19:47:12.013Z

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

Yuta Sakurai 2020-08-23T19:52:10.013200Z

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.
@arttuka 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 Sakurai 2020-08-23T19:58:05.013400Z

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

thheller 2020-08-23T20:02:10.013600Z

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

thheller 2020-08-23T20:02:33.013800Z

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

Yuta Sakurai 2020-08-23T20:18:26.014Z

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

thheller 2020-08-23T20:19:20.014200Z

Canvas is an actual component so that is a symbol

thheller 2020-08-23T20:19:32.014400Z

mesh is a not a component so should be :mesh

thheller 2020-08-23T20:20:09.014700Z

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

thheller 2020-08-23T20:20:41.015Z

(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"}]]])) )

thheller 2020-08-23T20:20:46.015200Z

this should be fine

thheller 2020-08-23T20:21:51.015400Z

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

Yuta Sakurai 2020-08-23T20:23:12.015700Z

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 Sakurai 2020-08-23T20:25:21.015900Z

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

thheller 2020-08-23T20:25:24.016100Z

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

thheller 2020-08-23T20:26:17.016300Z

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

Yuta Sakurai 2020-08-23T20:37:23.016500Z

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

thheller 2020-08-23T20:39:43.016700Z

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 Sakurai 2020-08-23T20:39:47.016900Z

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

thheller 2020-08-23T20:40:13.017100Z

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

thheller 2020-08-23T20:40:33.017300Z

are you calling that correctly?

thheller 2020-08-23T20:40:57.017500Z

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

Yuta Sakurai 2020-08-23T20:47:34.017700Z

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

thheller 2020-08-23T20:48:49.017900Z

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

thheller 2020-08-23T20:50:27.018100Z

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 Sakurai 2020-08-23T20:54:25.018300Z

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.

thheller 2020-08-23T21:09:12.018500Z

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

Yuta Sakurai 2020-08-23T21:12:53.018900Z

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