Fork me on GitHub
#reagent
<
2015-09-14
>
gadfly36101:09:09

Haven't used it yet, but reagent-forms has a typeahead: https://github.com/reagent-project/reagent-forms

sashton01:09:58

I've got a question about the React methods when using create-class. If I have a component which accepts multiple parameters how can I tell which parameter was updated in a call to :component-did-update?

sashton01:09:47

component-did-update passes has a signature of [this old-argv], but where can i get the new-argv to compare

sashton01:09:03

For example, in the following code, I want to put *** next to the val which was last updated:

(defn a-component [val1 val2]
  (let [last-updated (r/atom -1)]
    (r/create-class
      {:component-did-update (fn [this old-argv]
                               ; how do I find out if val1 was the cause of the update?
                               #_(if (????)
                                 (reset! last-updated 1)
                                 (reset! last-updated 2)))
       :reagent-render       (fn [val1 val2]
                               [:div
                                ; display the current value of each,
                                ; and indicate which was last updated
                                [:div (str "val1 " val1 (when (= 1 @last-updated) "  ***"))]
                                [:div (str "val2 " val2 (when (= 2 @last-updated) "  ***"))]])})))

(defn wrapper []
  (let [v1 (r/atom 0)
        v2 (r/atom 0)]
    (fn []
      [:div
       [:button {:on-click #(swap! v1 inc)} "Inc 1"]
       [:button {:on-click #(swap! v2 inc)} "Inc 2"]
       [a-component @v1 @v2]])))

sashton01:09:32

I'm not sure what to put in the component-did-update to determine which parameter caused the update.

sashton01:09:04

Thanks. I'm really looking for a way to respond to the update in the a-component however. I want my component to be smart enough to know when it needs to reset some local state.

sashton01:09:44

I mean, I could create local atoms just to store the most recent versions of each parameter, then I'd have the prior value from that and the new value from component-will-update, but I feel like there should be a better way.

gadfly36101:09:01

Haha agreed, I am sure there is a better way too!

sashton01:09:13

I looking more into the React lifecycle, and it sounds like maybe what I want to use is component-will-receive-props, but when I try to get the props out, all I get is nil:

:component-will-receive-props (fn [this new-argv]
                                       (println "WILL RECEIVE PROPS")
                                       (println "OLD" (r/props this)) ;always nil :(
                                       (println "NEW" new-argv))

gadfly36101:09:17

looks like a few fns in there, extract-props, get-props

sashton01:09:46

yeah, I did try that, same result. It looks like that's where (core/props) calls into

sashton01:09:24

hah! i hadn't tried util/get-argv. I think that works!

gadfly36102:09:16

sweet! can you share example?

sashton02:09:42

coming right up

sashton02:09:22

thanks @gadfly361 for the help

sashton02:09:39

I'm not sure what the first parameters in the vectors are

sashton02:09:59

maybe they are references to this or something?

gadfly36102:09:42

yeah, seems so. And this is great!! Thank you for figuring this out. Gotta save this example.

mikethompson04:09:07

I'm going to re-post @sashton 's snippet inline (rather than as a attechment) so it gets properly archived at: http://clojurians-log.mantike.pro/reagent/2015-09-14.html Otherwise it might be lost:

(defn a-component [val1 val2]
  (let [last-updated (r/atom -1)]
    (r/create-class
      {:component-will-update (fn [this new-argv]
                                (let [[_ old-v1 old-v2] (rutil/get-argv this)
                                      [_ new-v1 new-v2] new-argv]
                                  (reset! last-updated
                                          (cond
                                            (> new-v1 old-v1) 1
                                            (> new-v2 old-v2) 2
                                            :else 0))))
       :reagent-render        (fn [val1 val2]
                                [:div
                                 ; display the current value of each,
                                 ; and indicate which was last updated
                                 [:div (str "val1 " val1 (when (= 1 @last-updated) "  ***"))]
                                 [:div (str "val2 " val2 (when (= 2 @last-updated) "  ***"))]])})))


(defn wrapper []
  (let [v1 (r/atom 0)
        v2 (r/atom 0)]
    (fn []
      [:div
       [:button {:on-click #(swap! v1 inc)} "Inc 1"]
       [:button {:on-click #(swap! v2 inc)} "Inc 2"]
       [a-component @v1 @v2]])))

sashton04:09:10

Thanks @mikethompson, I'll also add an example to https://github.com/reagent-project/reagent-cookbook later this week

curtosis16:09:44

I'm having trouble wrapping my head around the right linkages... I have a textarea tied to a ratom, and I'd like to update the :rows attribute (also tied to a ratom) on it based on the contents (i.e., autoresize). I can get it to update the ratom fine with onChange, but how do I get the resize to run when I change the text externally?

curtosis18:09:20

ugh, nvm.. that one I solved by just updating both ratoms at the same time.

curtosis18:09:57

but now I have a different problem... I can't figure out why my :on-click isn't making it through:

curtosis18:09:14

(defn items []
  (let [visible? (r/atom false)]
    [:div
      [:h4 {:on-click #(swap! visible? not)} "Items"]
      [:table {:style {:display (if @visible? "block" "none")}}
        ;; table stuff here ]]]))

curtosis18:09:51

there's just no onclick handler in the generated html.

curtosis18:09:51

any clues where even to look?

curtosis18:09:08

oh... hmmm. actually, reagent is picking up the click (i.e. it's not on the element directly) but the component doesn't rerender.

gadfly36118:09:24

Try adding (fn [] ( ...) ) in between the let and the hiccup

gadfly36118:09:38

Think you are recreating the atom every render

sashton18:09:40

if you want to have local state, you need to return an function for the rendering

gadfly36118:09:54

Look at re-frame wiki form-2 components

curtosis18:09:05

I just read that last week.

gadfly36118:09:29

It bites us all

curtosis18:09:47

and voilà.

curtosis18:09:52

thanks for the reminder.

jaen18:09:54

Also - react doesn't really generate onClick handlers it lets all event bubble up and only then dispatches it's synthetic events and reacts to that. If you want to inspect handlers you need to use react devtools for chrome.

curtosis18:09:53

thanks, that's helpful to know. it also helps explain why the attribute is :on-click!

curtosis18:09:02

now I just have to build the same component 3x for the different backing collections.

curtosis18:09:35

always the do I macro this? question. 😛

jaen19:09:29

No, the :on-click gets translated to onClick anyway (om for example uses the original React casing), it's just that React's virtual DOM implementation doesn't hang the handlers on the real DOM, just deals with it internally.

curtosis19:09:18

ah ok. still good to know not to expect to see the handlers on the real DOM.

curtosis19:09:30

how would you create elements dynamically inside a component? I'm doing something like this:

[:tr
  (for [k (keys colmap)]
    [:th k])]

curtosis19:09:10

colmap is just a map: {"Label" :key-for-column}

jaen19:09:56

Like that, but I think you have to doall that

curtosis19:09:48

they both work. my idea was to create a generic component that does the hide/unhide toggle, and pass in the params it needs to get the right stuff out of the ratom.

curtosis19:09:57

but it's very warn-y. I get warnings about returning false from an event handler, and every element in the seq needing a unique key (doesn't like lots of :th in a single seq).

curtosis19:09:41

so I think I'm going too far down that rabbit hole

curtosis19:09:13

it's for debugging right now, so copy-paste might win over elegance.

jaen19:09:32

Yeah, you shouldn't return false but do (fn [e] ... (.preventDeafult e)) in the handler and keys are (I think) mainly for performance optimisation, so React knows when it should insert new DOM node and when it can reuse the existing one.

curtosis19:09:54

not sure where the handler is. 😉

jaen19:09:07

Hah, that's more of a problem then

sashton19:09:26

is it your :on-click from above?

curtosis19:09:55

though it doesn't throw that when I use it in the regular component

sashton19:09:11

try :on-click #(do (swap! ...) nil)

curtosis19:09:43

nope, still giving both warnings

gadfly36119:09:31

As for the unique ids, you will need to add ^{:key xxx} inside the for loop

curtosis19:09:46

where, inside? [:th ^{:key xxx}]?

curtosis19:09:12

still sort of voodoo to me simple_smile

sashton19:09:22

[:tr
        (for [k (keys {"Label" :key4col "Other" :blah})]
          ^{:key (first k)} [:th k])]

curtosis19:09:58

ah, ok. nifty.

gadfly36119:09:05

I dunno if you need first

jaen19:09:07

:on-click (fn [e] (swap! ...) (.preventDefault e)) is how the handler should look to avoid the warning

curtosis19:09:27

ewww. gross.

jaen19:09:00

Can't help that ; d

sashton19:09:43

i've got a wrapper to handle the react key generation in some of my stuff:

(defn with-react-keys
  "Creates a lazy-list of reagent components with React keys

   key-fn: Returns React the key for the element.   (fn [item index] )
   f:      React component to render the item       (fn [item] [:div ...])
   coll:   Collection of items
   args:   Any other args to be passed to the React component"
  [key-fn f coll & args]
  (if (satisfies? IAtom coll)
    (for [i (range (count @coll))
          :let [x (rc/cursor coll [i])]]
      (with-meta (into [f x] args)
                 {:key (key-fn x i)}))
    (map-indexed
      (fn [i x]
        (with-meta (into [f x] args)
                   {:key (key-fn x i)}))
      coll)))

sashton19:09:35

then i do:

[:thead
    [:tr (with-react-keys (fn [x i] i) table-header column-specs)]]

curtosis19:09:34

I might have to study that for a while to understand it.

curtosis19:09:15

hmm... @jaen that breaks the :on-click.

curtosis19:09:28

argh. nevermind. forgot to remove the #.

curtosis19:09:31

It's been a long day.