Search code examples
clojureclojurescriptreagent

How to pass new props to state of acomponent in Reagent?


I have a component:

(defn inner-input [cljs_element activeEl title]
  (let [form (atom title)]
    (fn [cljs_element activeEl title]
      [:input {:type "text"
           :onChange #(reset! form (.. % -target -value))
           :on-blur #(change-title cljs_element (.. % -target -value))
           :style {:display (if (:active (:node cljs_element)) "block" "none")
                   :width (* (+ 1 (count @form)) 8)
                   :max-width 730
                   :min-width 170}
           :value @form}])))

It is nested in other component:

(defn card-input [cljs_element activeEl]
  (fn [cljs_element activeEl]
    (let [title (:title (:node cljs_element))]
      [:div
        [inner-input cljs_element activeEl title]])))

When i type data to the input in the inner-input component, i need update the local state form. And when the outer component card-input updates i want to reset my form to new title prop from argument. How can i achieve that?

I tried put (reset! form title) between let and fn in the inner-input component but it will not help


Solution

  • You can use reagent/track! to listen to changes to title, and reagent/dispose to stop listening. You can alternatively use add-watch and remove-watch, but track is a more convenient syntax.

    (defn inner-input [title]
      (reagent/with-let
        [form (reagent/atom @title)
         watch (reagent/track! (fn [] (reset! form @title)))]
        [:label
         "Inner input"
         [:input {:on-change (fn [e]
                               (reset! form (.. e -target -value)))
                  :on-blur (fn [e]
                             (reset! title (.. e -target -value)))
                  :value @form}]]
        (finally
          (reagent/dispose! watch))))
    
    (defn card-input []
      (reagent/with-let
        [title (reagent/atom "hello")]
        [:div
         [:label "Title"
          [:input {:on-change (fn [e]
                                (reset! title (.. e -target -value)))
                   :value @title}]]
         [inner-input title]]))
    

    Now if you type in the inner input it will only update the outer when you exit the input box, but changing the outer title will immediately change the inner one. Is that what you wanted?

    But if you don't want to pass title as a ratom and have to pass it as a value, then instead you can compare it to the previous value to determine if it changed, and reset form only when it changes.

    (when (not= @previous-title title)
      (do (reset! previous-title title)
          (reset! form title)))
    

    This code can go in render seeing as it is safe to call when form changes... nothing will happen.