Clojurescript + reagent issue

I am working on a simple web application using clojurescript and reagent. I would like to create a simple "tab" component that will contain (to begin with) a text input component.

The app has 2 tabs and the user has the option to select the tab and I want to "save" the values ​​in each of these two tabs.

Here's the code:

(defn atom-input [value]
  [:input {:type "text"
           :value @value
           :on-change #(reset! value (-> % .-target .-value))}])

(defn simple-tab [index]
  (let [pg-index (atom 1)
        a (atom 0)]
    (fn []
    [:div
     [:h4 (str "index: " @index)]
     [atom-input a]])))

(defn main-page []
  (let [index (atom 0)]
    [:div.container
     [:div.row
      [:button {:on-click (fn [] (reset! index 0))} "select tab 1"]
      [:button {:on-click (fn [] (reset! index 1))} "select tab 2"]]
     [:div.row
      [simple-tab index]]]))

(defn ^:export run []
  (reagent/render-component
   (fn [] [main-page])
   (.-body js/document)))

      

The problem is that when I switch the tab, the components share the input field values ​​- what do I like to do wrong here?

Thank you so much for your help!

+3


source to share


3 answers


The problem is that you pass a (atom 0)

in the control atom-input

: [atom-input a]

. This caused the same atom value to split across your tabs.

If you don't want to share this value, you need to change a

to map: a (atom {})

and pass the map and index to atom-input

, for example:

(defn atom-input [value index]
  [:input {:type "text"
           :value (or (get @value index) "")
           :on-change #(swap! value assoc index (-> % .-target .-value))}])

(defn simple-tab [index]
  (let [pg-index (atom 1)
        a (atom {})]
    (fn []
      [:div
       [:h4 (str "index: " @index)]
       [atom-input a @index]])))

      



The best approach, IMHO, is to use a cursor, so you don't have to pass the index and the whole map in atom-input

, for example:

(defn atom-input [value]
  [:input {:type "text"
           :value (or @value "")
           :on-change #(reset! value (-> % .-target .-value))}])

(defn simple-tab [index]
  (let [pg-index (atom 1)
        a (atom {})]
    (fn []
      [:div
       [:h4 (str "index: " @index)]
       [atom-input (reagent/cursor [@index] a)]])))

      

+2


source


I think there are a couple of problems here because you are mixing data (state) of applications and displaying boolean data (i.e. DOM). If you are storing two different things, that is, maintaining the state of your application in one atom and the data related to displaying components in another, then things can be a little cleaner.

Your simple tab component doesn't need to know anything about the tab state. It just needs to be aware of the state of the application, that is, the value entered / stored using atomic input. So instead of passing this index, pass it the atom you want to use. more higher level logic is required to define the call. for example, if you had multiple tabs you might have something like

(condp = @index
  0 [simple-tab tab0-atom]
  1 [simple-tab tab1-atom]
  ...
  n [simple-tab tabn-atom])

      



or you can change the simple-tab so that the value passed to ie the index value is used as a key in the application state - a cursor would be the easiest, I think, ie

(def app-state (r/atom {:tabs {0 nil 1 nil}}})

(defn simple-tab [index]
  (let [val-cur (r/cursor app-state [:tabs index])]
    [atom-input val-cur]))

      

+2


source


You are using a form-2 component, that is, a component that returns a function.

More details here: https://github.com/Day8/re-frame/wiki/Creating-Reagent-Components#form-2--a-function-returning-a-function

This only calls the return function, so your input atoms use the same atom.

Also, you must use the same argument in the inner function

 (defn simple-tab [index]
      (let [pg-index (atom 1)
            a (atom {})]
        (fn [index]
          [:div
           [:h4 (str "index: " @index)]
           [atom-input a @index]])))

      

In your case, you are passing an atom, so it doesn't matter, but you might have a bug in the future if you forgot that.

As far as the broader architecture goes, I would advise you to use one global atom. Try to have as much state in that atom as possible and avoid the local state of the component, so it's easier to reason about.

You can also name your tabs something like: product: users and use a multimethod to display the correct tab based on the one you selected. It's easier to read and easier to add new tabs in the future.

0


source







All Articles