Fork me on GitHub
#clojurescript
<
2023-12-27
>
kah0ona08:12:17

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

kah0ona08:12:43

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

thheller09:12:20

you kind of didn't say what the actual issue is

kah0ona09:12:42

Sorry. I get an error sometimes, being this one: Invariant Violation: Minified React error #307; visit for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

kah0ona09:12:51

then the whole page turns white

kah0ona09:12:20

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

thheller09:12:13

what isn't entirely clear is what you are doing in the first place

thheller09:12:18

> Our widget just hooks into a div with <div id=“x”>

thheller09:12:27

doesn't sound like you are "hooking" into the original react

thheller09:12:47

so I assume its a document.getElementById at some point?

kah0ona09:12:58

Ok. Hooking wrong word. My re-frame app just searches for a <div id=“x”> to mount Mount. that’s the word. On.

thheller09:12:02

do you yourself use hooks or is the page itself breaking?

kah0ona09:12:43

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.

kah0ona09:12:58

Here: https://www.thegoodfood-company.com/catermonkey In some cases after a few seconds the page turns white

kah0ona09:12:10

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.

thheller09:12:07

well if its already react, the best way is to use that react

thheller09:12:11

if you have control over it that is

thheller09:12:34

I don't know wix or how it build pages but I do see a crapton of JS

kah0ona09:12:13

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)

thheller09:12:16

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

kah0ona09:12:41

is there a de facto way to deal with this?

thheller09:12:10

I can't say since I don't know how much access you have to the wix side

thheller09:12:28

does it allow you to include your own JS? does it support rending react components that JS provides?

thheller09:12:50

like instead of <div id="x"> can you make it use <YourComponent> directly?

kah0ona09:12:45

I think there’s not much access.. I doubt it. But that would be a new rabbit hole then indeed…

thheller09:12:28

as far as I can tell you are already using the external React somehow? or do you provide it in your build

thheller09:12:47

there is so much JS already, so not sure whats yours. I assume the cm-shop.js

kah0ona09:12:49

I provide it in my build. But it’s a diferent version (and hard to keep in sync anyway)

kah0ona09:12:54

cm-shop is mine, yes.

kah0ona09:12:01

(You deserve a medal for even looking into this lol)

thheller09:12:46

what it looks like to me is that your variant of react overrides the one that is already present in the page

thheller09:12:59

so you should make your build use that react instead

kah0ona09:12:03

I think that’s very true, as i insert it only after 1 sec

kah0ona09:12:09

yeah and how can i do that?

kah0ona09:12:40

i’ve never tried that 🙂

thheller09:12:43

not entirely sure, since you are not using shadow-cljs 😛

kah0ona09:12:55

so shadow-cljs is able to do that?

thheller09:12:58

how are you handling JS deps?

kah0ona09:12:58

using figwheel-main btw

thheller09:12:08

with webpack or just cljsjs?

thheller09:12:19

with figwheel not sure, it doesn't have an equivalent option

thheller09:12:33

but should be possible via :foreign-libs

kah0ona09:12:34

yeah webpack now

thheller09:12:42

ok webpack can do this for sure too

kah0ona09:12:43

well, webpack first, and then via deps.edn

kah0ona09:12:03

clojure -Amin

kah0ona09:12:06

ok i’m going to RTFM regarding shadow-cljs on this point

thheller09:12:57

thats the option in webpack, so basically you map to the globals "react": "React", "react-dom":"ReactDOM"

👍 1
kah0ona09:12:20

my package.json doesn’t include react though, that’s in deps.edn where i include reagent 1.2.0

thheller09:12:01

package.json is not involved in your build, it only handles downloading and managing your dependencies

kah0ona09:12:16

ok I think i’ll try to make a strategy where I build two versions of this app, via a circle CI build

thheller09:12:44

what do you mean by

thheller09:12:45

> well, webpack first, and then via deps.edn

kah0ona09:12:48

to ensure i don’t break things, these widgets run on dozens of websites… But now I have this Wix-built site

thheller09:12:58

I assume you run webpack over the figwheel output?

kah0ona09:12:46

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

kah0ona09:12:12

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

kah0ona09:12:35

tbh, I made this work a few years ago and haven’t much looked into the build process recently

thheller09:12:44

ah that makes things easier

thheller09:12:57

dist/index.bundle.js I assume is the file webpack builds

kah0ona09:12:10

//webpack.config.js
module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'index.bundle.js'
  }
}

thheller09:12:33

and ./src/js/index.js is the file that has the react require?

kah0ona09:12:45

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;

kah0ona09:12:18

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

thheller09:12:33

ok so then webpack isn't providing the react, CLJSJS is

kah0ona09:12:18

but i think reagent includes it as transient dependency no?

p-himik09:12:49

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

thheller09:12:51

trying to remember how :foreign-libs works 😛

kah0ona09:12:18

yeah it should run anywhere; we just never stumbled upon running it inside a React DOM @U2FRKM4TW

thheller09:12:29

thats not quite the issue

thheller09:12:45

the issue is that your build is replacing the already existing global React and ReactDOM

thheller09:12:21

so the goal is to not have those globals replaced

thheller09:12:51

but that doesn't quite work with CLJSJS since its all globals based

thheller09:12:14

so your only option is to replace it entirely, but then it breaks for all pages not having react

thheller09:12:40

FWIW this issue doesn't exist in shadow-cljs 😛

p-himik09:12:51

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?

thheller09:12:22

no need to, it is never declared as a global anywhere

p-himik09:12:03

Mmm, wasn't React itself setting some globals? Maybe I'm misremembering or maybe they have changed it.

thheller09:12:31

no, react doesn't set anything global. in general npm packages that behave properly don't

p-himik09:12:25

Ah, I'm thinking of __REACT_DEVTOOLS_GLOBAL_HOOK__. Probably irrelevant here.

thheller09:12:03

thats basically how cljsjs is using the global

thheller09:12:26

ie. cljsjs builds it to create the React global, which the CLJSJS mapping then uses

thheller09:12:05

ok with using foreign-libs+cljsjs I see no way to fix this

thheller09:12:40

but you can drop the CLJSJS part since you have webpack already setup

thheller09:12:06

import {nl} from 'date-fns/locale';
import react from "react";
import rdom from "react-dom";

window.nlNL = nl;
window.MyReact = react;
window.MyReactDOM = rdom;

thheller09:12:11

{:file           "dist/index.bundle.js"
 :provides       ["nl" "react" "react-dom"]
 :global-exports {nl nlNL
                  react "MyReact"
                  "react-dom" "MyReactDOM"}}
 

thheller09:12:23

I think that might be enough

p-himik09:12:56

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

thheller09:12:06

could obviously be cleaner but I think that side steps the cljsjs package entirely

thheller09:12:22

FWIW the shadow-cljs build config is {:target :browser :output-dir "out" :modules {:cm-shop {:entries [catermonkey-shop.core]}}}

thheller09:12:31

all the rest disappears 😛

thheller09:12:53

well, some of your code may change depending on how you use all those globals and stuff

kah0ona09:12:23

| so the goal is to not have those globals replaced ^-- yes this makes sense to me

kah0ona09:12:01

I will try your suggestion @U05224H0W, very much appreciated y’all looking into this!

kah0ona09:12:30

I might actaully indeed switch to shadow-cljs, seeing your comment now

kah0ona10:12:44

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.

kah0ona10:12:58

ah the datepicker

thheller10:12:36

the old one isn't provided by your build, it is by the wix stuff

kah0ona10:12:01

yeah it was, because of cljsjs/react-datepicker

kah0ona10:12:12

i added :exclusions now, see what that does

thheller10:12:27

you probably just need to add that to the :provides and stuff, same as react

kah0ona10:12:48

ah and not use cljsjs at all

kah0ona10:12:00

ok good point

thheller10:12:14

makes no sense to use cljsjs if you have the webpack setup going anyways

kah0ona10:12:17

yeah ok … it’s that i started off with cljsjs, until I ran into a wall and then added webpack 🙂

kah0ona10:12:27

so historic reasons, but will refactor that now too

kah0ona20:12:51

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.

kah0ona20:12:32

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

thheller21:12:26

not sure. I'd debug this by looking at the generated JS code

thheller21:12:47

like where is it accessing reagent.dom.global$module$react_dom and is it ever assigned to anything anywhere?

kah0ona21:12:48

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.

thheller21:12:30

could be that :foreign-libs just needs a tweak, I'm not entirely sure how this works since shadow-cljs doesn't have this 😛

kah0ona21:12:05

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

kah0ona21:12:14

and choose which to use to build / start

kah0ona21:12:21

thinking ‘accretion’ here 😄

kah0ona21:12:20

and a related question: do you happen to know if js/React global object isn’t needed for local development with reagent?

kah0ona21:12:31

because it appears so 🙂

thheller22:12:04

yes, everything can coexist. you can even continue to use your deps.edn

thheller22:12:10

just need to add a shadow-cljs.edn basically

thheller22:12:42

the global isn't needed. shadow-cljs doesn't have it at all and people use reagent just fine

thheller22:12:41

{:deps true
 :builds
 {:app
  {:target :browser
   :output-dir "out"
   :modules {:cm-shop {:entries [catermonkey-shop.core]}}}}}

thheller22:12:47

this is the basic shadow-cljs.edn you need

thheller22:12:08

and add the shadow-cljs dep to deps.edn. thats it basically

kah0ona22:12:16

yeah so i have a production build and a local repl based workflow, so I probably add two builds for that right?

kah0ona22:12:25

:dev and :app or something

thheller22:12:41

each build in shadow has a built-in dev and release mode

1
kah0ona22:12:53

ok that’s nice 😄

thheller22:12:02

npx shadow-cljs watch app for the dev/repl variant

thheller22:12:19

npx shadow-cljs release app for the optimized release build

kah0ona22:12:49

and it probably integrates with emacs/cider-jack-in-cljs in some fashion :thinking_face:

thheller22:12:02

cider has support yes, no clue how that works though since I don't use it

kah0ona22:12:04

which is of a lesser concern, but habits… 😄

thheller22:12:00

it might require undoing some "hacks" you did since webpack no longer provides this nlNL thing

thheller22:12:14

on the flip side you can probably just require it directly now

kah0ona22:12:53

yeah but that’s very doable. not THAT big of an app…

kah0ona22:12:47

imma rtfm for a bit, and then only select the good questions to be asked on slack 😉

kah0ona12:12:36

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.

kah0ona12:12:49

and most importantly: the problem I originally had, is solved

kah0ona12:12:05

As our build no longer pollutes the global window object

kah0ona12:12:24

Thanks yall!!!

🎉 4
kah0ona08:12:48

apart from using an iframe

Ivana13:12:27

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.

p-himik14:12:26

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.

Ivana14:12:24

Thanks, will read the link first

Ivana14:12:36

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.

p-himik14:12:01

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.

Ivana14:12:32

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

clyfe15:12:31

reitit instead secretary

1
clyfe15:12:37

I find shaddow-cljs more popular than figwheel

Ivana15:12:15

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 😄

p-himik15:12:19

It's OK, don't sweat it.

🔥 1
1
Nikolas Pafitis18:01:50

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 hxbut I personally prefer the abstractions provided by reagent, and the clean state management provided by re-frame.