Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Computing values with re-frame #154

Open
njj opened this issue Feb 7, 2019 · 3 comments
Open

Computing values with re-frame #154

njj opened this issue Feb 7, 2019 · 3 comments

Comments

@njj
Copy link

njj commented Feb 7, 2019

With the re-framing example, everything is handled through the events function:

(def events
  {:get (fn [path] @(re-frame/subscribe [:value path]))
   :save! (fn [path value] (re-frame/dispatch [:set-value path value]))
   :update! (fn [path save-fn value]
              ; save-fn should accept two arguments: old-value, new-value
              (re-frame/dispatch [:update-value save-fn path value]))
   :doc (fn [] @(re-frame/subscribe [:doc]))})

This works fine for initializing and updating values but what if we wanted to calculate the value of a non-editable field (similar to the BMI example). I have a field that comes from the backend that is non-editable and I want to show the calculation of that field to the user before sending it back over. Is there a way to update a specific field in the doc outside of the the events function setup?

@yogthos
Copy link
Member

yogthos commented Feb 7, 2019

You can handle computed values via re-frame events, e.g:

(ns example.core
  (:require
   [reagent.core :as r]
   [re-frame.core :as re-frame]
   [reagent-forms.core :refer [bind-fields]]))

(re-frame/reg-event-db
 :init
 (fn [_ _]
   {:doc {}}))

(re-frame/reg-sub
 :doc
 (fn [db _]
   (:doc db)))

(re-frame/reg-sub
 :value
 :<- [:doc]
 (fn [doc [_ path]]
   (get-in doc path)))

(defmulti rule (fn [_ path _] path))

(defn bmi [{:keys [weight height] :as doc}]
  (assoc doc :bmi (/ weight (* height height))))

(defmethod rule [:height] [doc path value]
  (bmi doc))

(defmethod rule [:weight] [doc path value]
  (bmi doc))

(defmethod rule :default [doc path value]
  doc)

(re-frame/reg-event-db
 :set-value
 (fn [{:keys [doc] :as db} [_ path value]]
   (-> db
       (assoc-in (into [:doc] path) value)
       (update :doc rule path value))))

(def events
  {:get (fn [path] @(re-frame/subscribe [:value path]))
   :save! (fn [path value] (re-frame/dispatch [:set-value path value]))  
   :doc (fn [] @(re-frame/subscribe [:doc]))})

(defn row [label input]
  [:div
   [:div [:label label]]
   [:div input]])

(def form-template
  [:div
   [:h3 "BMI Calculator"]
   (row "Height" [:input {:field :numeric :id :height}])
   (row "Weight" [:input {:field :numeric :id :weight}])
   (row "BMI" [:label {:field :label :id :bmi}])])

(defn home-page []
  [:div [:h2 "Welcome to Reagent"]
   [bind-fields form-template events]])

@njj
Copy link
Author

njj commented Feb 7, 2019

@yogthos Thanks this is super helpful! It might benefit others to have your re-frame documentation in the readme reflect this.

The only issue I have w/ this implementation is it assumes the only computed value is the BMI, and I may have several forms with varying computed values. Is there a recommended way to refactor and abstract this such that you can pass in a variable rule? I tried doing so on my own but ran into a ton of issues. Something a long the lines of:

(re-frame/reg-event-db
 :set-value
 (fn [{:keys [doc] :as db} [_ path value rule]]
   (-> db
       (assoc-in (into [:doc] path) value)
       (when rule
         (update :doc rule path value)))))

@yogthos
Copy link
Member

yogthos commented Feb 7, 2019

Note that the is actually a multimethod that's keyed on the path with the default behavior of doing nothing. So, the pattern I would suggest would be to create rule defmethods for the paths that should trigger business rules to recalculate values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants