Fork me on GitHub

Hi, I was wondering if folks had advice on implementing mobile-ready forms using re-frame. I am moving through the book ‘Web Development with Clojure’ and using its pattern for building forms, but it doesn’t seem to work for phones. I am not sure if I am missing something, or if there is a better pattern? I’ll add more context/code as a thread to this message. Thank you for any pointers you could give!


In the book, they recommend creating db entries for the form fields, and then using a reagent/track atom for when someone is typing into a field, then dispatching an event when they move away from the form to add the input to the field in the db.


So you have a component like so:

(defn text-input
  [{val :value
    attrs :attrs
    :keys [on-save]}]
  (let [draft (r/atom nil)
        value (r/track #(or @draft @val ""))]
    (fn []
      [:input.cg__form_text (merge attrs
                                   {:type :text
                                    :placeholder "…"
                                    :on-focus #(reset! draft (or @val ""))
                                    :on-blur (fn []
                                               (on-save (or @draft ""))
                                               (reset! draft nil))
                                    :on-change #(reset! draft (.. % -target -value))
                                    :value @value})])))


put into a form like so:

(defn new-game-form
    [:label {:for :p1} "Player One "]
    [text-input {:attrs {:name :p1}
                 :value (rf/subscribe [:new-game/field :p1])
                 :on-save #(rf/dispatch [:new-game/set-field :p1 %])}]]
    [:label {:for :p2} "Player Two "]
    [text-input {:attrs {:name :p2}
                 :value (rf/subscribe [:new-game/field :p2])
                 :on-save #(rf/dispatch [:new-game/set-field :p2 %])}]]
   [:input.form_submit {:type :submit
                                    :on-click #(do (.preventDefault %)
                                                   (rf/dispatch [:new-game/start
                                                                 @(rf/subscribe [:new-game/form])]))
                                    :value "Start game"}]])


and then I have the re-frame events and subs for this form, like so:

;; events

 [(rf/path :form/fields)]
 (fn [fields [_ id value]]
   (assoc fields id value)))
;; subs
 (fn [db]
   (:new-game-form db)))

 :<- [:new-game/form]
 (fn [fields [_ id]]
   (get fields id)))


The issue i am having is that the dispatch event to update a field is only called on-blur, and the change itself is being tracked in a component-only atom. But if i try this form on a phone, the second field often doesn’t get saved. This is because I am touching the field, filling out the name, then tapping the submit button. I believe on the phone the blur doesn’t register as happening because of the difference of touch versus click.


it only works if i tap somewhere else on screen and then tap submit, which is unintuitive. I’ve tried to not use the tracking atom, and instead update the field in the db on-change but that causes intense lag for typing into the field. I am feeling discouraged because this feels like an incredible amount of work to recreate basic html events. I feel I am missing something. Is there a better pattern for making a form that updates the db upon submit, is as responsive as native html, and works on phones? Thank you!


Try dispatch-sync for input fields.


Ah, that’s a good idea! with dispatch-sync, would i not need the local atoms or the on-blur effect, but instead just use the on-change event, and have it call #(rf/dispatch-sync [:set-field (.. % -target -value)])?


You don't need local atoms here at all. And that track as well. I don't know why the book overcomplicates things.


This is fantastic. Thank you!

👍 2