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?@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
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.just like in JS the names need to come from somewhere. so the JS examples likely also just import
or require
them?
Thanks for just quickly reply, @thheller. Now trying your suggest, plz wait... // I really appreciate what you've developped shadow-cljs.
hmm I guess it might want [:mesh .. [:boxBufferGeometry ]]
then?
not sure if reagent maybe only allows :div
and such
apparently react-three-fiber does some jsx magic that converts into three.js expressions:
> <mesh />
simply is another expression for `new THREE.Mesh()`
there is no mention of this in the docs I can find?
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", ...)
the quote was from here: https://github.com/react-spring/react-three-fiber#does-it-have-limitations
ah. I think just comparing to what is going to happen under the hood at runtime. not what the actual JSX produces.
JSX can't express :div
vs Component
(keyword vs symbol) so I think their logic is lower-case = html tag, upper case = component
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.I will try using raw THREE.Mesh()
instead of mesh
JSX tag.
Thank you all!
are you sure you didn't have any leftover [mesh ...]
or so? :mesh
definitely doesn't leave behind a undefined
while mesh
would
(and you also should be getting a compiler warning for)
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"}]]]
Canvas
is an actual component so that is a symbol
mesh
is a not a component so should be :mesh
this Assert failed: Invalid Hiccup form
is completely expected after Use of undeclared Var myns/mesh
(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"}]]])) )
this should be fine
although I'm not too sure about :scale
and :args
. maybe that needs to be :scale #js [1 1 1]
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)
Okey, args must be js array, so fixed to (clj->js [1 1 1]) and no changes.
why is this react-dom? aren't you supposed to use something from react-three-fiber
to do the rendering?
or maybe thats what Canvas
is for? I'm really just guessing here 😛
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).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.
Ah, maybe the problem is just from Canvas
, not JSX tag(`mesh` and so on)...?
oh wait ... you are using useRef
which means this must be a function component
are you calling that correctly?
where do you use fn$animation-lantern
? would help to see the full code instead of guessing 😉
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))
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
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
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.
knew I saw this error before. maybe that helps tracking it down? https://github.com/reagent-project/reagent/issues/502
Thank you, I just find the issue now. Now trying to read carefully. (maybe already you know, my English skill is very poor)