This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-27
Channels
- # announcements (2)
- # babashka (24)
- # beginners (116)
- # cider (7)
- # cljsrn (6)
- # clojure (38)
- # clojure-bay-area (4)
- # clojure-europe (3)
- # clojure-losangeles (1)
- # clojure-norway (10)
- # clojurescript (171)
- # datomic (16)
- # honeysql (3)
- # improve-getting-started (1)
- # introduce-yourself (2)
- # java (12)
- # malli (5)
- # membrane (2)
- # pedestal (3)
- # shadow-cljs (79)
- # spacemacs (6)
- # xtdb (10)
Hello Folks,
I have built a reagent
/ re-frame
widget that can be embedded in any website.
I now run into the issue, that when there’s already React on the page where it is embedded, and my widget renders in the React Dom rendered by the other react, it becomes a https://legacy.reactjs.org/docs/error-decoder.html/?invariant=307 .
Is there any way around this?
The https://reactjs.org/link/invalid-hook-call state this:
“In general, React supports using multiple independent copies on one page (for example, if an app and a third-party widget both use it). It only breaks if require('react')
resolves differently between the component and the react-dom
copy it was rendered with.”
Context: some customers of ours run a website built with Wix, which appearantly is mostly rendered using React.
Our widget just hooks into a div with <div id=“x”>
This div IS in the DOM (as I inspect it, and probably rendered via React). I tried inserting it via setTimeout
after say 2 seconds (as a hack), but that doesn’t work either.
I’m wondering if it’s possible at all at this point
Sorry. I get an error sometimes, being this one:
Invariant Violation: Minified React error #307; visit
it doesn’t even happen all the time … like if somehow the div where I mount it on is not rerendered by the parent React i’m ‘fine’.
Ok. Hooking wrong word. My re-frame app just searches for a <div id=“x”> to mount Mount. that’s the word. On.
I don’t use hooks as far as I’m aware, not sure if reagent deos something like that under the hood though. And the stacktrace of the hook invariant problem comes from the ‘parent React’, ie the react already on the page.
Here: https://www.thegoodfood-company.com/catermonkey In some cases after a few seconds the page turns white
oh and sometimes it does render. It’s a mess… I’m trying to approach this from a first principle point.
Is it at all possible to mount via document.getElementById
which is a div probably rendered by react (that’s already on the page) itself (not even sure, but thinking this might be the case)
Or is there some conceptual thing in React that allows me to build ‘plugins’ for existing React apps where I don’t have access to the source.
Can I compile it without myself also bundling the react? I think the answer is yes, just have never tried that (not using shadow-cljs btw, ironically)
the issue with getElementById
is that if react decides for any reason to remove the target div and re-render it then your component will be gone
does it allow you to include your own JS? does it support rending react components that JS provides?
I think there’s not much access.. I doubt it. But that would be a new rabbit hole then indeed…
as far as I can tell you are already using the external React somehow? or do you provide it in your build
I provide it in my build. But it’s a diferent version (and hard to keep in sync anyway)
what it looks like to me is that your variant of react overrides the one that is already present in the page
with shadow-cljs it would just be https://shadow-cljs.github.io/docs/UsersGuide.html#js-resolve
thats the option in webpack, so basically you map to the globals "react": "React", "react-dom":"ReactDOM"
my package.json doesn’t include react though, that’s in deps.edn
where i include reagent 1.2.0
package.json is not involved in your build, it only handles downloading and managing your dependencies
ok I think i’ll try to make a strategy where I build two versions of this app, via a circle CI build
to ensure i don’t break things, these widgets run on dozens of websites… But now I have this Wix-built site
I meant:
I have circleCI run npm run build
which does: webpack -p --devtool source-map"
and then it runs: clojure -Amin
being:
{:min {:main-opts ["-m" "cljs.main" "-co" "prod.cljs.edn" "-c" "catermonkey-shop.core"]}
,,,
}
;;prod.cljs.edn : production cljs compiler configuration
{:optimizations :advanced
:language-in :ecmascript-next
:npm-deps false
:foreign-libs [{:file "dist/index.bundle.js"
:provides ["nl"]
:global-exports {nl nlNL}}]
:externs ["resources/public/js/sentry.extern.js"
"resources/public/js/react-datepicker-locale.extern.js"
"resources/public/js/catermonkey-config.extern.js"]
:output-to "out/cm-shop.js"
:output-wrapper true ;;wraps it in a IIFE
:source-map "out/cm-shop.js.map"}
tbh, I made this work a few years ago and haven’t much looked into the build process recently
//webpack.config.js
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'index.bundle.js'
}
}
hmm no … I assume this was to get the react datepicker working with Dutch locale…
//index.js, here we import and expose npm modules to the global context
import {nl} from 'date-fns/locale';
window.nlNL = nl;
here’s my other relvant config files
;;deps.edn
{:paths ["src" "resources" "test"]
:deps
{org.clojure/clojure {:mvn/version "1.11.0"}
org.clojure/clojurescript {:mvn/version "1.11.60"}
reagent {:mvn/version "1.2.0"}
re-frame {:mvn/version "1.3.0"}
com.rpl/specter {:mvn/version "1.1.4"}
cljs-ajax {:mvn/version "0.8.4"} ;
day8.re-frame/http-fx {:mvn/version "0.1.6"}
com.andrewmcveigh/cljs-time {:mvn/version "0.5.2"} ;date/time
alandipert/storage-atom {:mvn/version "2.0.1"}
com.taoensso/timbre {:mvn/version "4.10.0"}
com.taoensso/tempura {:mvn/version "1.2.1"}
cljsjs/moment-timezone {:mvn/version "0.5.11-1"}
garden {:mvn/version "1.3.9"}
com.cemerick/url {:mvn/version "0.1.1"}
cljsjs/react-datepicker {:mvn/version "2.3.0-0"}
cljsjs/qrcode-generator {:mvn/version "1.4.4-0"}
re-val {:mvn/version "0.1.0-SNAPSHOT"}
clj-commons/secretary {:mvn/version "1.2.4"}
com.bhauman/figwheel-main {:mvn/version "0.2.18"}
cljsjs/date-fns {:mvn/version "1.29.0-0"}
com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}}
:aliases {:min {:main-opts ["-m" "cljs.main" "-co" "prod.cljs.edn" "-c" "catermonkey-shop.core"]}
:build-dev {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}
:test {:extra-paths ["test"]
:extra-deps {olical/cljs-test-runner {:mvn/version "3.8.0"}}
:main-opts ["-m" "cljs-test-runner.main"]}}}
> these widgets run on dozens of websites… But now I have this Wix-built site Just in case - it makes it sound as if the same widget must be runnable on React-based websites and plain JS websites, or websites that use anything else other than React. Of if they use some incompatible version of React.
yeah it should run anywhere; we just never stumbled upon running it inside a React DOM @U2FRKM4TW
the issue is that your build is replacing the already existing global React
and ReactDOM
so your only option is to replace it entirely, but then it breaks for all pages not having react
thats not quite the issueIt's the issue with Wix in particular. If the code/build is changed in such a way that the widget always uses React provided by the platform, the issue with overwriting React on Wix will be replaced with an issue of missing React or incompatible React on some other platfom. Makes it sound like there has to be a way to isolate React and all other dependencies, or separate builds must be provided for all sorts of platforms. > FWIW this issue doesn't exist in shadow-cljs Why? Can you isolate React there in some way?
Mmm, wasn't React itself setting some globals? Maybe I'm misremembering or maybe they have changed it.
no, react doesn't set anything global. in general npm packages that behave properly don't
import {nl} from 'date-fns/locale';
import react from "react";
import rdom from "react-dom";
window.nlNL = nl;
window.MyReact = react;
window.MyReactDOM = rdom;
{:file "dist/index.bundle.js"
:provides ["nl" "react" "react-dom"]
:global-exports {nl nlNL
react "MyReact"
"react-dom" "MyReactDOM"}}
FWIW, if switching to shadow-cljs is an option, I myself would consider it before other options. :) (Won't help with the main page's React being able to erase your whole widget though.)
FWIW the shadow-cljs build config is {:target :browser :output-dir "out" :modules {:cm-shop {:entries [catermonkey-shop.core]}}}
well, some of your code may change depending on how you use all those globals and stuff
I will try your suggestion @U05224H0W, very much appreciated y’all looking into this!
ok this does something 🙂 only now it seems there’s still both a React global and a MyReact global, ie. the old one is not removed (it’s my version as React.version gives 16.8.1, rather than 16.10.0 of the parent) This might be some reagent configuration I think.
yeah ok … it’s that i started off with cljsjs, until I ran into a wall and then added webpack 🙂
so it almost feels unfair to ask this here, as I first went for the webpack route rather than shadow-cljs
...
I think i configured everything well, ie I have js/MyReactDOM
available, and also (require '[react-dom :as react-dom])
works fine if I eval that on the REPL from my buffer.…
But I get this now: reagent.dom.global$module$react_dom is undefined
upon calling reagent-dom/render
to mount the thing. Which is weird because I can navigate to the code, eval the underlying react-dom/render
function, and it does exist. So I wonder what global$module$react_dom
is in the first place.
Could one of you provide any clue as to where to look to debug this?
I feel I’m almost there to make sure I can run the app without putting js/React
on the global namespace. I got rid of cljsjs
, it’s all succesfully built in via webpack now… so bit of confused atm.
(ns ^:figwheel-hooks catermonkey-shop.core
(:require
[react]
[react-dom]
[reagent.core :as reagent]
[reagent.dom :as reagent-dom]
,,,,))
;; this is that call btw
(defn mount [el]
(reagent-dom/render [error-boundary [webshop-panel-main]] el))
like where is it accessing reagent.dom.global$module$react_dom
and is it ever assigned to anything anywhere?
some ‘progress’… it seems to pass that point when I bind it to window.React again, rather than window.MyReact seems a local dev issue. Ok I will try to use shadow-cljs tomorrow.
could be that :foreign-libs
just needs a tweak, I'm not entirely sure how this works since shadow-cljs doesn't have this 😛
can I relatively easily change a figwheel-main project to a shadow-cljs project? could in theory both configs coexist, bit like you could have project.clj and deps.edn
and a related question: do you happen to know if js/React global object isn’t needed for local development with reagent?
the global isn't needed. shadow-cljs doesn't have it at all and people use reagent just fine
{:deps true
:builds
{:app
{:target :browser
:output-dir "out"
:modules {:cm-shop {:entries [catermonkey-shop.core]}}}}}
yeah so i have a production build and a local repl based workflow, so I probably add two builds for that right?
and it probably integrates with emacs/cider-jack-in-cljs in some fashion :thinking_face:
it might require undoing some "hacks" you did since webpack no longer provides this nlNL thing
OK. Had a few niggles, but man. It’s way better now just by using shadow-cljs. I should’ve listened, but my memory of configuring figwheel somewhat demotivated me to switch tooling. But after all said and done the amount of config code is like WAY less now.
Can I compile it without myself also bundling the react? I think the answer is yes, just have never tried that (not using shadow-cljs btw, ironically)
Hi! Not for starting a holywar, but need to chose libs for not so hardweight and not so complex frontend. Which are famous in current season? Reagent + reframe + secretary + garden + figwheel (or figwheel-main) is a good set or today another beasts are more famous? Thanks for possible advices.
As usual, it depends. For the kinds of apps that I develop, I go with re-frame and shadow-cljs. Other details might vary. It might very well be that the app that you're building will be totally fine with an approach outlined here: https://code.thheller.com/blog/shadow-cljs/2023/07/16/applying-the-art-of-cljs-frontend.html It might be something else. The "not so hardweight and not so complex frontend" part doesn't really give any details.
Under "not so hardweight and not so complex frontend" I mean spa with simple client routing, state, get/post rest api and some interaction with user - nothing special. I mostly afraid that you may say something like "Reagent doesn't support react hooks, so noone use it nowadays, so use some-another-react-wrapper" etc.
Reagent does support hooks, with a bit of additional work, and plenty of people use it. But there are other wrappers as well, I know of Helix and UIx.
Thanks. I enumerated the stack which I had the deal with on all my work projects, so I have an experience in it. But now I need to chose the stack for new project and just trying to decide - use known familiar stack or I have to switch to another one due some reasons. If not, I'd like to use what I enumerated
Ok, thanks! Actually I may write my own custom client rouiting instead of secretary/reitit, and use ratoms instead of re-frame, the main question - is reagent still ok or I should use another thing maybe even not react-based. Or complex frameworks like Luminus etc. If it's ok, I'd prefer reagent 😄
I'd go for reagent
, re-frame
, shadow-cljs
, reitit
for routing, and for UI components I like to use arttuka/reagent-material-ui
but now there's also https://github.com/elastic/eui-cljs which is an official wrapper for Elastic UI though I haven't tried it (although you don't really need a wrapper to use any of the common react libraries, but it's a good Quality of Life). Alot of people now are opting for more lighter wrappers of react, usually hook-based, like uix
or helix
or hx
but I personally prefer the abstractions provided by reagent, and the clean state management provided by re-frame.