changing db value in input form in reframe - clojurescript

I've been trouble by an updating issue in reframe.
db.cljs:
{:apple 16}
event.cljs
(rf/reg-event-db
:update-apple-size
(fn [ db [_ size] ]
(assoc db :apple size)))
subs.cljs
(rf/reg-subs
::apple-size
(fn [ db ]
(get db :apple)))
views.cljs
[:input {:type :text
:on-change (rf/dispatch [:update-apple-size (.. % -target -value js/parseFloat))]
:value #(rf/subscribe [::apple-size] }]
The issue happed when user udpating with decimal number of the size. When user try to update current 16 to 16.5.
User will strike . ,but this will be trigger on-change event and wont' update the db value. ( {:apple 16} => {:apple 16.0} ) ?. Then the subscribe will render "16.0" to UI as "16" ?
that's why user can't really append decimals after an integer in input element ?
one way I can think of is stringyfy all values in db but this will create lots of trouble since there are lots of calculation assuming the values are number in the db.
Any advise will be helpful !

Note that js/parseFloat parses a string with a decimal dot into an integer:
cljs.user=> (js/parseFloat "16.")
16
One solution can be to save into db two values:
the value of input as a string
the parsed value of input
Then input will display a string with a decimal dot. See example:
Db:
(def default-db
{:input-val "5"
:input-num 5})
Views:
[:input {:type "string"
:value #(re-frame/subscribe [::subs/input-val])
:on-change #(re-frame.core/dispatch [:change-val (-> % .-target .-value)])}]
[:div
[:p #(re-frame/subscribe [::subs/input-num])]]
Subs:
(re-frame/reg-sub
::input-val
(fn [db] (:input-val db)))
(re-frame/reg-sub
::input-num
(fn [db] (:input-num db)))
Events:
(re-frame/reg-event-db
:change-val
(fn [db [_ val]]
(-> db
(assoc :input-val val)
(assoc :input-num (js/parseFloat val)))))

Related

Cljs re-frame is not showing element as expected

I'm experimenting with Cljs re-frame, in which I'm trying to show a cell in UI with the below function,
(defn cell
[{:keys [id value on-change]}]
[(r/adapt-react-class Box) {:key id
:type :number
:ml 2
:content-editable (nil? value)
:on-change on-change
:border "2px solid #10AF34"
:width "30px"
:height "30px"
:align "center"}
value])
When I try to call the method directly from a method that is being invoked for rendering, it works,
(defn app
[]
(cell {:id "1-1"
:value nil
:on-change #(js/alert "in cell 1 1")}))
Whereas, when I try to wrap it in another method and call, it is not working
(defn grid
[data]
(cell {:id "1-1"
:value 1
:on-change #(js/alert "in cell 1 1")}))
(defn app
[]
(grid (generate-data)))
If someone wants to try it out, the grid/cell is available here and the app/rendering function is available here.
Your issue might stem from using [(r/adapt-react-class Box) …] - I‘m not sure it’s problematic, but I‘d either def the result of adapt-react-class and reuse it, or use the [:> Box …] syntax.

re-frame: reset atom after dispatch

I have this form:
(defn input-question
[]
(let [new-question (reagent/atom "")]
(fn []
[:div
[:input {:type "text"
:value #new-question
:on-change #(reset! new-question (-> % .-target .-value))}]
[:input {:type "button"
:value "Save new question"
:on-click #(re-frame.core/dispatch [:create-question #new-question])} ] ])))
How can I reset #new-question to "" (empty string) after the dispatch?
You can use reset! on the ratom after dispatching:
#(do (re-frame.core/dispatch [:create-question #new-question])
(reset! new-question ""))
to reset it after dispatching the value.
You probably want to review the re-frame effects docs:
https://github.com/Day8/re-frame/blob/master/docs/EffectfulHandlers.md
https://github.com/Day8/re-frame/blob/master/docs/Effects.md
Note that you can also use dispatch-n:
https://github.com/Day8/re-frame/blob/master/docs/API.md#dispatch-n
and you might want to use the fn syntax instead of the #(...) shorthand function syntax:
:input {:type "button"
:value "Save new question"
:on-click (fn []
(re-frame.core/dispatch [:create-question #new-question])
(reset! new-question "")) } ]
You can also use both events and subs to keep as much logic out of your view code as possible. This means you will end up with many any events and subs, however this is by design and idiomatic to re-frame. This makes your re-frame code easier to understand, decoupled and more testable. Here is an example:
(rf/reg-fx
:save-question
(fn [question]))
;; Handle creating a question
(rf/reg-sub
:new-question-value
(fn [db _]
(get-in db [:new-question :value])))
(rf/reg-event-db
:on-new-question-change
(fn [db [_ value]]
(assoc-in db [:new-question :value] value)))
(rf/reg-event-fx
:on-save-question-click
(fn [{:keys [db]} _]
{:db (assoc-in db [:new-question :value] "")
:save-question (get-in db [:new-question :value])}))
(defn input-question
[]
(let [new-question-value (rf/subscribe [:new-question-value])
on-save-question-click #(rf/dispatch [:on-save-question-click])
on-new-question-change #(rf/dispatch [:on-new-question-change (.. % -target -value)])]
(fn []
[:div
[:input {:type "text"
:value #new-question-value
:on-change on-new-question-change}]
[:input {:type "button"
:value "Save new question"
:on-click on-save-question-click}]])))
Some extra notes about this code:
You should namespace your events and subs keys to prevent naming clashes
You should define a function and pass that into reg-fx, reg-event-db, reg-event-fx & reg-sub. Doing this can make the code more testable by allowing test code to call the function handler directly. However you can still test using Day8/re-frame-test but it's a little harder.

Antizer form submit data handling

I would like to use ant design components in reagent through antizer but I can't figure out how to extract fields values from the form after submitting. I can't find anything in the documentation.
Someone more expert than me has resolved this problem?
If you supply a callback to the ant/validate-fields function, it will receive two arguments: errors and values.
If the input is valid, errors will be null.
The second argument will always contain the current form data.
;; Receive the output of `ant/validate-fields`, and react accordingly
(defn submit-form-if-valid
[errors values]
(if (nil? errors)
(println "Form is valid." values)
(println "Validation failed." errors)))
(defn sample-form
(fn [props]
(let [my-form (ant/create-form)
submit-handler #(ant/validate-fields my-form submit-form-if-valid)]
[:div
[ant/form {:on-submit #(do
(.preventDefault %)
(submit-handler))}
[ant/form-item ...]
[ant/form-item ...]
[ant/form-item ...]
[ant/form-item
[ant/button {:type "primary"
:html-type "submit"}
"Submit"]]]])))
Note: Personally, I only use this function to check for errors. My form data is continually recorded in the app-db every time the user changes a field. So my submit handler looks more like this:
(defn submit-form-if-valid
[errors _]
(when (nil? errors)
(dispatch [:sample-form/submit!])))
My re-frame events look something like this. One event to update the form data in DB (using the key/value pair provided by a form input), and another to actually submit the form:
(reg-event-db
:sample-form/update-value
[(path db/path)]
(fn [db [_ k v]]
(assoc-in db [:sample-form-data k] v)))
(reg-event-fx
:sample-form/submit!
[(path db/path)]
(fn [{:keys [db]} _]
(let [form-data (:sample-form-data db)])
;; ... send data to back-end, handle the response, etc.
))
And each of my form inputs invoke that event like this:
[ant/form-item
(ant/decorate-field my-form "Thing #1" {:rules [{:required true}]}
[ant/input {:on-change #(dispatch [:sample-form/update-value :thing1 (-> % .-target .-value)])}])]
Hope this helps!

Let Sub Component React on Parent's State Plus Having its Own State

Consider the following Reagent components:
(defn sub-compo [n]
(let [state (r/atom {:colors (cycle [:red :green])})]
(fn []
[:div {:style {:color (-> #state :colors first)}
:on-mouse-move #(swap! state update :colors rest)}
"a very colorful representation of our number " n "."])))
(defn compo []
(let [state (r/atom {:n 0})]
(fn []
[:div {:on-click #(swap! state update :n inc)}
"Number is " (#state :n) "."
[sub-compo (#state :n)]])))
I tried to make up an example, in which a sub component should depend on the state of its parent component. However the sub component should have an internal state as well. The above does not work properly. When the state in compo changes sub-compo is not initialized a new.
Which would be the way to go here, in order to let sub-compo be in sync with comp? Whenever the state of comp changes sub-comp should actually be initialized anew, meaning it's color state is set to the initial value again.
Here's a solution that does at least what I want. It uses a cursor and a watch. But maybe there is a simpler way to do so, anyways:
(defn sub-compo [n]
(let [init-state {:colors (cycle [:red :green])}
state (r/atom init-state)]
(add-watch n :my (fn []
(reset! state init-state)))
(fn []
[:div {:style {:color (-> #state :colors first)}
:on-mouse-move #(swap! state update :colors rest)}
"a very colorful representation of our number " #n "."])))
(defn compo []
(let [state (r/atom {:n 0})]
(fn []
[:div {:on-click #(swap! state update :n inc)}
"Number is " (#state :n) "."
[sub-compo (r/cursor state [:n])]])))
The above does not work properly. When the state in compo changes
sub-compo is not initialized a new.
This is because the inner function of sub-compo needs to receive the argument n as well.
Whenever the state of comp changes sub-comp should actually be
initialized anew, meaning it's color state is set to the initial value
again.
You could use :component-will-receive-props for this.
This worked for me:
(defn sub-compo [n]
(let [init {:colors (cycle [:red :green])}
state (r/atom init)]
(r/create-class
{:component-will-receive-props
(fn [this [_ n]]
(reset! state init))
:reagent-render
(fn [n]
[:div {:style {:color (-> #state :colors first)}
:on-mouse-move #(swap! state update :colors rest)}
"a very colorful representation of our number " n "."])})))

def'ine a value with a dynamic name

So I want to pass a function to the "name" portion of def.
The problem is:
"First argument to def must be a Symbol"
I'm trying to for instance do:
(def serverNumber 5)
(def (str "server" serverNumber) {:id serverNumber :value 4939})
But I can't find annnnnnny way to do this. Any help would be beyond appreciated :)
First, I have to note that this seems like a bad idea. Why are you trying to generate defs with dynamically-generated names? (As #pst already pointed out, maps are the usual solution for creating bindings with dynamically-generated identifiers.)
Assuming you have a legit reason for doing this (maybe it's part of some library functionality where you're generating defs for the user), you can accomplish this with a macro:
(defmacro def' [sym-exp & other-args]
`(def ~(-> sym-exp eval symbol) ~#other-args))
(def serverNumber 5)
(def' (str "server" serverNumber) {:id serverNumber :value 4939})
Note that this only works at the top level (since macros are run at compile time). If you want to do this in a function or something then you just need to use eval:
(defn def'' [sym-exp & other-args]
(eval `(def ~(-> sym-exp eval symbol) ~#other-args)))
If you just want to create a bunch of agents, maybe something like this will work:
(def servers
(vec (for [i (range 5)]
{:id i :value 4939})))
Then you can just access them by index:
(servers 0)
; => {:id 0, :value 4939}
The run-time equivalent of def is intern:
(intern *ns*
(symbol (str "server" server-number))
{:id server-number :value 4939})