peterf1222
peterf1222

Reputation: 41

clojurescript + reagent issue

I am working on a simple web-app using clojurescript and reagent. I would like to create a simple "tab" component, which will contain (for starters) a text-input component.

The app has 2 tabs and the user has the option to choose a tab and I want to "preserve" 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 values of the input field - what am I please doing wrong here?

Thank you so much for your help!

Upvotes: 3

Views: 1273

Answers (3)

jvuillermet
jvuillermet

Reputation: 23

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

When doing so, only the returned function is called so your atom-inputs are sharing the same atom.

Also, you should 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 this is not important but you could have error in the future if you forgot this.

Concerning the broader architecture, I would advise you to use a single global atom. Try to have as much as possible state in this atom and avoid component local state so this is easier to reason about.

You could also name your tabs like :product :users and use a multimethod to render the correct tab based on the selected one. This is easier to read and easier to add new tabs in the future.

Upvotes: 0

Tim X
Tim X

Reputation: 4235

I think there are a couple of problems here because you are mixing up application data (state) and display logic data (i.e. DOM). If you keep the two things distinct i.e. maintain your application state in one atom and data relating to component display in another, then things may be a bit cleaner.

Your simple-tab component does not need to know anything about the tab state. It just needs to know about the app state i.e. the value entered/stored via atom-input. Therefore, rather than passing it index, pass it the atom you want it to use. this would require some higher level logic to determine the call. for example, if you had a number of 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 could modify simple-tab so that the value passed in i.e. index value is used as a key into the app-state - a cursor would be easiest I think i.e.

(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]))

Upvotes: 2

sindux
sindux

Reputation: 606

The problem is you're passing a (atom 0) to the atom-input control: [atom-input a]. This caused the same atom value to be shared between your tabs.

If you don't want to share the value, you'll need change a to a map: a (atom {}) and pass the map and the index to atom-input, e.g.:

(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]])))

A better approach, IMHO, is to use cursor so you don't need to pass the index & the whole map to atom-input, e.g.:

(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)]])))

Upvotes: 2

Related Questions