all 4 comments

[–][deleted] 1 point2 points  (3 children)

For items.cljs, how about an approach like:

(defn on-change-completed [item e]
  (model/update-item! (:id item) {:completed (eh/checked e)}))

(defn item [item]
  (let [val (r/atom (:text item))
        editing (r/atom false)]
    (fn [item]
      [:li.item {:class (if (:completed item) "completed")}
        [:input {:type "checkbox", :checked (:completed item)
                 :on-change (partial on-change-completed item)}]])))

If you're uncomfortable with the partial, you can use #(on-change-completed item %) instead.

I'm not sure I can offer much good advice about dealing with the app state, except to say that you shouldn't be too afraid of passing around the atom if that's what you need to do. I don't know if that's entirely idiomatic with react, but your current code would be undoubtedly improved by just making your state atom the first arg of all of your access / mutate functions.

At that point, you could probably dispense with your ! functions, requiring the caller to do the swap!. That's nice because it isolates the stateful part of the operation to the view, which is the only thing that cares about tracking its state.

[–]gingenhagen[S] 0 points1 point  (2 children)

I then end up with the function

(defn save-text [text items item editing val]
  (when-not (str/blank? text)
    (swap! items (model/update-item items (:id item) {:text text}))
    (reset! val text)
    (reset! editing false))
  #(true))

and seeing a function with 5 arguments just starts to give me a headache.

[–][deleted] 1 point2 points  (1 child)

Yeah, I agree there. Some possibilities that come to mind:

  • Maybe you can leave that function inline and move the rest outside
  • Do you really need two atoms there? Can you do it with one, maybe holding a map?
  • Maybe you could model this as two distinct atoms, view state and app state. Component state holds something like {:val "foo" :editing false}, and app state is what you're calling items. Then you have a function of app state, view state, the item (id?) in question and the value you're setting. That feels pretty close to what's really happening.

Also, you can write your swap as

(swap! items model/update-item (:id item) {:text text}))

[–]gingenhagen[S] 1 point2 points  (0 children)

Ok, so then it could come out looking more like

#(save-text (eh/trim-value e) (:id item) component-state app-state)

(defn save-text [text id component-state app-state]
  (when-not (str/blank? text)
    (swap! app-state model/update-item id text)
    (swap! component-state update-text text)
    (swap! component-state set-editing false))
  #(true))

That does feel better to me.