Fork me on GitHub
#reagent
<
2023-10-03
>
Aron Gabor08:10:22

hi, how much should I expect reagent to work with react 18.2 I am having the classical state is updated but component doesn't rerender problem and I have no idea why, from other examples online it looks like i am doing the same thing, but I've found 2 year old github issues unsolved with the same problem, so I am thinking, it's probably best to abandon ship?

rolt08:10:18

I'm using react 18.2 with reagent and haven't encountered any issues, are you using any specific react features ?

Aron Gabor09:10:59

hmm, I didn't think there will be an answer, but I am happy there is. give me a minute to bring alive one of my earliest minimal examples that I was struggling with

Stef Coetzee11:10:47

I haven't had issues with React 18. Example setup below (excerpts only, for brevity):

# Main CLJS app file

(ns client.app.main
  (:require [reagent.core :as r]
            [reagent.dom.client :as rdc]))

(defonce app-root
  (rdc/create-root (.getElementById js/document "app")))

(def functional-compiler
  (r/create-compiler {:functional-components true}))

(defn render []
  (rdc/render app-root
              <add your app here>
              functional-compiler))

(defn ^:dev/after-load start []
  (js/console.log "Start")
  (render))

(defn ^:dev/export init []
  (js/console.log "Initialize")
  (start))

(defn ^:dev/before-load stop []
  (js/console.log "Stop"))
I typically add the start, init, and stop functions early in development to help me slot functionality in if need be at a later stage.
# package.json

{
  "devDependencies": {
    "shadow-cljs": "^2.25.2",
  },
  "dependencies": {
    "react": "18.2.0",
    "react-dom": "18.2.0"
  }
}

Aron Gabor11:10:38

sorry, this took longer than expected, actual work intervened, this example doesn't work for me at all, the console log is shown, so the render supposedly runs, but there is no visual update for the counter's value:

(ns app.browser
  (:require
   [reagent.core :as r]
   [reagent.dom.client :as rdomc]
   ["react" :as react]))

(def !counter (r/atom 0))

(defonce react-root
  (rdomc/create-root (js/document.getElementById "app")))

(defn comp1
  []                                
  [:button  {:class "button-class"
          :on-click  #(swap! !counter inc)} 
   "+"])

(defn app []
  (js/console.log @!counter)
  [:div.container
   (str @!counter)
   [comp1]])

(defn ^:dev/after-load start []
  (rdomc/render react-root [:> react/StrictMode {} [:f> app]]))

(defn init []
  (js/console.log "init")
  (start))

(defn ^:dev/before-load stop []
  (js/console.log "stop"))

rolt12:10:50

[app] vs [:f> app]

rolt12:10:47

app is already a reagent component so I don't think :f> will work

Stef Coetzee12:10:00

This works for me:

(ns app.main
  (:require [reagent.core :as r]
            [reagent.dom.client :as rdc]))

(def !counter (r/atom 0))

(defonce app-root
  (rdc/create-root (.getElementById js/document "app")))

(def functional-compiler
  (r/create-compiler {:functional-components true}))

;; Components

(defn increment-counter []
  [:button
   {:on-click (fn [] (swap! !counter inc))}
   "+"])

(defn app []
  (js/console.log (str "Counter: " @!counter))
  [:div
   [:div
    (str "Counter: " @!counter)]
   [:div
    [increment-counter]]])

(defn render []
  (rdc/render app-root
              [app]
              functional-compiler))

;; ---

(defn ^:dev/after-load start []
  (js/console.log "Start")
  (render))

(defn ^:dev/export init []
  (js/console.log "Initialize")
  (start))

(defn ^:dev/before-load stop []
  (js/console.log "Stop"))

Stef Coetzee12:10:56

I found the docs on the compiler (covers functional component implementation) quite helpful. https://github.com/reagent-project/reagent/blob/master/doc/ReagentCompiler.md#reagent-compiler

Aron Gabor12:10:32

:f> was necessary originally because the actual problem came up when I was integrating cytoscape via an effect hook

rolt12:10:24

you should pass a react function component to :f>, not a reagent component

Aron Gabor12:10:30

But I can confirm that removing it makes the button work

rolt12:10:52

the reagent component will be either a function component or a class component depending on the reagent compiler used

Aron Gabor12:10:24

I will try this alternate solution to use functional components

Aron Gabor13:10:36

well, the demo I constructed works without the :f> and my useeffect also works with the functional-compiler, but the state update -> component render still doesn't happen, even though I removed all :f>

Aron Gabor13:10:58

is there some sure fire way to debug/troubleshoot this phenomenon?

Aron Gabor13:10:27

looks to me that when I call my hook, everything else in the component is ignored afterwards

Aron Gabor13:10:44

(def data (r/atom nil))

(defn setup-cyto [{:keys [cs config]}]
  (cs config))

(defn use-cytoscape [element config els]
  (react/useEffect
   (fn []
     (when (some? @element)
       (let [js-els (clj->js  els)
             cy (setup-cyto {:cs cs
                             :config (config element js-els)})]
         (-> cy
             .nodes
             (.on "click" #(reset! !koala (.id (.-target %)))))
         (reset! data cy)
         (.resize cy)
         (.fit cy)
         #(.destroy cy)))
     js/undefined)))

Aron Gabor13:10:55

just the hook for completeness

rolt13:10:42

I use react hooks without issues, but have never used useEffect (defn app [] [:f> use-cytospace]]) doesn't work ?

rolt13:10:19

i'm just not sure you can use the ratom inside the useEffect function, and still have reactivity on the ratom change

👀 1
rolt13:10:24

but maybe just deref-ing the ratom in use-cytospace (outside useEffect) would work ? it's a bit of a hack 😕

Aron Gabor13:10:31

what do you mean never used useEffect? how do you integrate third party tools then?

Aron Gabor13:10:01

did you mean this line (.on "click" #(reset! !koala (.id (.-target %)))))

rolt13:10:36

i'm not sure i follow, you were saying the component doesn't get re-rendered. What i'm saying is that I don't think use-cytoscape will be re-rendered if element changes here (unless you deref element after useEffect (defn use-cytoscape [] (react/useEffect ...) @element [:<>])

rolt13:10:35

or is it when data changes ? I don't see data's value being used anywhere

Aron Gabor13:10:45

will try in a moment, just work wouldn't be in the way

Aron Gabor14:10:07

I am afraid no amount of derefing changes this. I think I am doing something wrong if it works for you, but I don't know what to look at. I have a bunch of console logs and only the ones that are before the use-cytoscape hook run more than once, all the rest runs at the first click but not again

Aron Gabor14:10:18

i put derefs everywhere 😄

rolt14:10:09

(def x (reagent/atom 0))

(defn a
  []
  [:button
   {:onClick #(swap! x inc)}
   "click me"])

(defn b
  []
  (react/useEffect
   (fn [] (println "clicked !" @x) js/undefined))
  @x
  [:<>])

(defn app []
  [:<>
   [a]
   [:f> b]])

rolt14:10:38

with the following, I do get the println everytime I click the button in a

Aron Gabor14:10:30

the console.log appears for me too, but the value doesn't change

Aron Gabor14:10:57

probably your code would though, as earlier the demo was working for me too without the :f>

rolt14:10:47

which value ?

Aron Gabor14:10:21

the one being reset by the hook.

rolt14:10:32

are you sure @element is not nil/false ?

Aron Gabor14:10:55

no, !koala 🙂

Aron Gabor14:10:32

and I can check the element, but I would guess right now that it's not empty, the cytoscape graph being visible in it

rolt14:10:58

ok so it doesn't really have anything to do with useEffect that line is a bit too specific to your lib so I can't help

Aron Gabor15:10:28

thanks, you already helped immensely, at least i can be confident that this should work