Fork me on GitHub
#reagent
<
2022-10-28
>
Sam Ritchie14:10:59

Question: is there some utility code in reagent for identifying props like :on-click, etc, prefixed with :on- , or are those automatically camel-cased on the way into react and not explicitly handled?

Lone Ranger14:10:38

not sure if this is exactly what you're asking but reagent automatically converts the standard props hooks on hiccup components to camelCase<->kebab-case for you. This only becomes an issue if you're using 3rd party components with reagent/adapt-react-class, where you may end up having to use camelCase. e.g., antd and material-ui components I will use :onClick instead of :on-click. Yes, I could write a wrapper, but I have things to do so I write :onClick and keep it movin'

Lone Ranger14:10:30

so to be more explicit,

(defn standard-reagent-component []
  [:button {:on-click #(println "works")} "Click Me"])

(def antd-button (reagent/adapt-react-class (.. antd -Button))

(defn antd-button []
  [antd-button {:onClick #(println "this works")} "No click me"])

Lone Ranger14:10:56

ah I forgot you're writing a library

Lone Ranger14:10:03

and you're looking to do the same trick

Sam Ritchie14:10:31

That is helpful! Yeah in this case I need to pull them out and register them as event handlers

Sam Ritchie14:10:23

Ugh, I am realizing that I still probably do want to use force-update to make my API cleaner. Passing these props to children to force a re-render is not great for folks writing new components. Is there a reliable way to force a render call to every child of a component if the component’s render is called? Maybe the question is really “in what lifecycle method is one supposed to call force-update?”

Lone Ranger15:10:43

first of all I'm certainly no stickler for convention or common wisdom but I am obligated as a member of the neckbeard guild to say that "way lies madness" and ask if there's a reason that the rerender can't be done with the normal dataflow concerns?

Lone Ranger15:10:59

if there is, happy to go down that rabbit-hole but is kind of a fragile rabbit-hole

Lone Ranger15:10:58

that being said you might be doing it conceptually correctly already -- there's a number of "gotchas" with rendering components in reagent

Lone Ranger15:10:18

and it's different pre-version 0.8, and then again different after I think 1.0 or 1.1

Lone Ranger15:10:43

so for instance in current reagent,

(defn form-1
  [data]
  [:div
   [:p (str "My data is: " data)]
   [form-1-subcomponent-1 data]
   [form-1-subcomponent-2 data]
   [form-1-subcomponent-3 data]])
this should cause a rerender of all subcomponents

Lone Ranger15:10:54

when the value of data changes

Lone Ranger15:10:26

does that not work in your case?

Sam Ritchie15:10:42

I am constitutionally 100% on board with the dataflow model; the “issue” here I am trying to avoid for users comes from 2 things, as background. 1. scenes are build with big calls like like

var board = JXG.JSXGraph.initBoard('jxgbox',{boundingbox:[-5,5,5,-5]});
var A = [], s = [], B = [], c = [], r = [], k;

var attA = {name:'',strokeColor: '#7355ff', fillColor: '#7355ff'};
A[0] = board.create('point', [2.5, -3], attA);
A[1] = board.create('point', [2, 4], attA);
A[2] = board.create('point', [-2.5, 3], attA);
A[3] = board.create('point', [-4, -2], attA);
A[4] = board.create('point', [0, -4], attA);
and look like http://jsxgraph.org/wiki/index.php?title=A_5-circle_incidence_theorem instead of numbers, points can depend on the names of previous points for their coordinates. so really any time ANY point’s props change, the thing to do is remove everyone and re-add everyone to the board

neckbeard 1
Sam Ritchie15:10:59

(because the dependency tree is not really usable for this)

Sam Ritchie15:10:23

so you are completely right; the right thing to do is just pass a changed property to everyone and they will all re-render

Lone Ranger15:10:03

ok so I'm thinking you might want to consider not using reagent subcomponents at all for the points, unless there is some reason to do that

Lone Ranger15:10:38

I'm thinking you might want a form-3 component as the top level for the board and then using procedural calls to manipulate the board itself when the data changes

Sam Ritchie15:10:13

the thing I was trying to avoid was this. when I make a component that, say, adds multiple points, building on the Point primitive, I have to do this:

(defn PointLine [x props]
  (letfn [(f [i]
            [Point [(- i) i]
             (assoc props
                    :name (str i)
                    :strokecolor "red")])]
    (into [:<>] (map f) (range x))))
if the user forgets to pass on the props then these points won’t be able to trigger updates

Lone Ranger15:10:14

Unless there are major performance concerns I'd probably just wipe the whole board and reinstantiate it from the new data

Sam Ritchie15:10:07

@U3BALC2HH yeah absolutely, that is functionally what is happening here. it’s just so nice to have the user PRETEND they are doing the declarative thing:

(v/html
 [:<>
  [jsx/JSXGraph {:boundingbox [-8 4 8 -5]
                 :showCopyright false
                 :axis true}
   [jsx/Point [x y] {:name "A" :strokecolor "red" :on-drag on-drag}]
   [jsx/Point [-xf -yf] {:name "B" :strokecolor "red"}]
   [jsx/Point [0 0] {:name "C" :strokecolor "blue" :on-drag on-drag}]
   [jsx/Angle ["A" "C" "B"] {}]]
  [v/inspect @!state]])

Lone Ranger15:10:19

have you considered something though like

(def board [board-data]
   ...)
where board-data is a map?

Lone Ranger15:10:59

then they could be actually doing a declarative think, like the comp-viz and data-viz guys I think, it's been awhile and I can't remember the names of all the slick sci-cloj visualization libraries

Sam Ritchie15:10:18

@U3BALC2HH say more; what would be inside that function?

Sam Ritchie15:10:53

or that is supposed to be a def, so they are actually writing a data structure that I then parse and turn into board.create calls

Sam Ritchie15:10:44

yeah, that is actually my next step; • the user will write something like that on a server • I will (using #C035GRLJEP8) send that over to the browser • I will then expand it out into these reagent components And the reason I am doing the reagent thing is that I want to be able to stuff a jsxgraph instance into an existing reagent render tree, so users can build rich interfaces with this stuff

Sam Ritchie15:10:09

I think you are right that, given the state of things, I should accept that I am going to have to change the props and not try to stray further from the good road here

Lone Ranger15:10:24

(defn board [board-data]
  (let [active-board-data (reagent/atom board-data)
        board-ref (reagent/atom nil)]
    (letfn [(update-board-data! []
              (let [current-board @board-ref
                    ]
                ;; implementation tbd
                (wipe-board! current-board)
                (draw-board! current-board @active-board-data)))
            (board-init [this]
              (when-let [dom-node (reagent.dom/dom-node this)]
                (reset! board-ref dome-node)))]) 
    (reagent/create-class
     {:display-name "board"
      :component-did-mount (fn [this props]
                             (board-init this)
                             (update-board-data))
      :component-did-update update-board-data
      :reagent-render
      (fn [board-data]
        [v/html
         [:<>
          [jsx/JSXGraph {:boundingbox [-8 4 8 -5]
                         :showCopyright false
                         :axis true}]]])})))

Lone Ranger15:10:43

so I don't know the API/details but that's kind of the gist of it

Lone Ranger15:10:33

you'd probably have to stick some :onClick or some other handlers on there to manipulate active-board-data and fiddle with one or two other things -- that's def more pseudocode than working code, but that's the gist of it

Lone Ranger15:10:51

if efficiency became a thing you could diff the new map against the old map and do your own modifications but I doubt that's an issue unless you're dealing with a ton of data

Lone Ranger15:10:29

also I just copy/pasta'd a bunch of your code, I don't know what v/ is or jsx/JSXGraph

Lone Ranger15:10:04

so would that work or do you think you still need to go down the reactive wrapper route?

Lone Ranger15:10:12

I'm just concerned about the double-bookkeeping

Lone Ranger15:10:39

you'd have to write form-3 components all the way down to double-manage state for react and for the imperative/procedural graph builder API

Lone Ranger15:10:56

( also in case it's not obvious, draw-board! would use the procedural API )

Sam Ritchie16:10:35

hey, this is helpful, let me think for a while