Fork me on GitHub
#reagent
<
2020-03-29
>
otwieracz08:03:03

How can I generate custom methods for Reagent/React component?

otwieracz08:03:42

I need to provide getValue() and getInputNode() for my component to be used by something else.

juhoteperi08:03:40

@slawek098 Are those instance methods or static methods? If instance methods, use r/create-class

otwieracz08:03:06

Instance methods.

juhoteperi08:03:26

I think docs don't currently have section just for this, but other parts have examples of this: https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#error-boundaries here get-derived-state-from-error is static method (it doesn't take this parameter) but it works because Reagent knows the name and has special handling. If you need to add static properties currently you'd need to take the return value from create-class and attach the properties to that.

juhoteperi08:03:06

Just add :get-value (or :getValue) etc. to the create-class parameter, should work automatically.

otwieracz08:03:37

Hm, I alredy tried it.

juhoteperi08:03:12

If the method take React props or other parameters, you will get the unmodified values from React, which might be a bit different to what default methods show as Reagent converts some parameters.

otwieracz08:03:16

(defn editor [data]
  (r/create-class
   {:display-name "editor"
    :getInputNode (fn [& args] (js/console.log (str args)))
    :reagent-render (fn [data]
                      [:div (str data)])}))

otwieracz08:03:37

I prepare list of columns with map:

otwieracz08:03:45

(map (fn [id text]
           {:key       id
            :name      text
            :editable  true
            :formatter (r/reactify-component formatter)
            :editor    (r/reactify-component editor)})
         (range)
         column-names)

otwieracz08:03:51

This should reasemble code from:

juhoteperi08:03:59

Do you see the column headers etc? So the column options are working?

otwieracz08:03:11

So:

class ColorEditor extends React.Component {
  (...)
  getInputNode() {
    return ReactDOM.findDOMNode(this).getElementsByTagName("input")[0];
  }
  (...)
}

const columns = [
  { key: "id", name: "ID" },
  { key: "title", name: "Title" },
  { key: "labelColour", name: "Label Colour", editor: ColorEditor }
];

otwieracz08:03:33

Yes, everything else is working.

otwieracz08:03:37

Formatter is also working.

otwieracz08:03:00

But here I am getting:

otwieracz08:03:05

react-data-grid.js:2 Uncaught TypeError: n.getEditor(...).getInputNode is not a function

juhoteperi08:03:07

And you are using latest Reagent?

otwieracz08:03:29

let mee see which is latest..

otwieracz08:03:43

Yes, it's the most recent.

juhoteperi08:03:04

reactify-component might be unncessary here, because create-class already returns Component class, no need to convert it. But might be it just doesn't do anything, don't remember.

juhoteperi08:03:01

Try :editor (editor "foobar)

otwieracz08:03:14

Why (editor "foobar")?

juhoteperi08:03:40

(You could also change editor from defn to def, and remove the call and parameter)

otwieracz08:03:18

somethin is working!

juhoteperi08:03:51

If I remember correctly, reactify-component when provided with function value (`editor`) will convert the function to Reagent class component, and that is not the same class as what you create inside the function. The render of this created class is rendered using the class you created.

juhoteperi08:03:07

So in this case, the parent component doesn't see the class you created but Reagent wrapper class.

juhoteperi08:03:19

So you need to directly provide the class you create.

juhoteperi08:03:09

(def editor (r/create-class ...)) ... :editor editor is probably the best way to handle this case

otwieracz08:03:30

Thank you very much!

hindol12:03:59

Hi, does Reagent support dynamic variables as a mechanism to communicate across the stack? I want to pass a callback from the grand-parent to grand-child but the component in-between does not use it.

p-himik12:03:05

Not an expert here, but I would guess that it doesn't. A child component may be re-rendered without the re-rendering of the parent, thus making the dynamic variable not bound to the correct value. I would just wrap and create the grandchild right there in the parent, and pass the wrapped component to the opaque child.

hindol12:03:46

Thanks for the advice! Actually I simplified a bit while asking the question. There is probably 2/3 levels which don't use it. Passing anything down is a pain. I don't want to immediately jump to re-frame if I can help it.

p-himik12:03:55

You can still use a wrapper, no matter how nested the structure is. Well, unless you dynamically construct the structure and pass it down as a parameter to the parent component.

juhoteperi12:03:36

Not really. The component render function can be called several times etc. not all times will have the same dynamic context.

markx21:03:46

Hi! I’m reading the reagent tutorial. https://cljdoc.org/d/reagent/reagent/0.10.0/doc/tutorials/when-do-components-update-#lifecycle-functions Here it says component-did-update will not be called when re-render is triggered by ratom change. Is this true?

markx21:03:54

(defn test-comp []
  (let [counter (r/atom 0)]
    (r/create-class
      {:display-name "test-comp"
       :component-did-update
       (fn [this]
         (prn "component did update"))

       :reagent-render
       (fn []
         [:div
          [:div @counter]
          [:button {:on-click  #(swap! counter inc)} "click"]])})))

markx21:03:31

I tried with this simple component, and apparently it does trigger! What’s wrong here?

David Pham12:03:25

If you make a print statement inside your reagent render, you will see that your component is recreated again everytime you click on the button. So compnoent did update is called because it is called everytime reagent-render is called.

David Pham12:03:40

(defn test-comp [counter]
  (r/create-class
   {:display-name "test-comp"
    :component-did-update
    (fn [this]
      (prn "component did update"))
    :reagent-render
    (fn []
      [:div
       [:button {:on-click  #(swap! counter inc)} "click"]])}))

(defcard-rg component-test
  (let [counter (r/atom 0)]
    (fn []
      [:<>
       [:div @counter]
       [test-comp counter]])))

David Pham12:03:21

you can see that Created is only called once, and component did update is never called whenever you click the button.

David Pham12:03:32

I dunno if this answer your question.

David Pham12:03:54

but I guess it makes sense that did updated is not called because the reference (the atom) is not change.

markx02:03:37

> If you make a print statement inside your reagent render, you will see that your component is recreated again everytime you click on the button. Well render function is called when you click the button, but it doesn’t mean the component is re-created.. That’s obvious.

markx02:03:49

And your second example, sorry but I’m not sure what you are trying to say here. It doesn’t even deref the atom, so sure it will never update.

David Pham06:03:26

Ok, sorry for being confusing. I think you should try to construct an exemple where component-did-update is called without being re-rendering and then you could construct an exemple where your atom would not trigger the said methods.

David Pham06:03:17

About render, you could use the component -did-mount method to check if it is created again?

markx02:03:37

> If you make a print statement inside your reagent render, you will see that your component is recreated again everytime you click on the button. Well render function is called when you click the button, but it doesn’t mean the component is re-created.. That’s obvious.