Using Light Table, how do I tell Om to re-render the DOM after eval'ing a modified Om function?
Forcing a swap! on the main state atom has no effect:
(swap! app-state identity)
Cycling routes explicitly with (swap! app-state assoc :current-page :about) and back to home with (swap! app-state assoc :current-page :home), reflect changes to the home page.
My browser is connected to Light Table and I can trigger alerts with, e.g. (js/alert "hi")
Calling the root again also triggers a render:
(root app app-state
{:target (. js/document
(getElementById "site"))})
Why doesn't Om trigger a render on app-state atom swap!?
In Om, you should modify your application state atom with om/update! and om/transact!.
Related
Hellow everyone.
This is a block of code in events.cljs file. There is a button on the page that I want when I click that, a text appears on the page and disappear after 3 seconds. Here I wanted to assoc the text to the db after clicking the button and dissoc it after 3 seconds but there is an error that Thread namespace not found, from Thread/sleep line.
Can anyone help me how I should fix that please?
Thank you
(rf/reg-event-db
::niloofar
(fn [db [_]]
(do
(assoc db :greeting "hi")
(Thread/sleep 3000)
(dissoc db :greeting))))
You should create a new effect that dissocs the value and use it with :dispatch-later, something like:
(rf/reg-event-fx ::show
(fn [{db :db} _]
{:db (assoc db :greeting "hi")
:dispatch-later {:ms 3000 :dispatch [::-hide]}))
(rf/reg-event-db ::-hide
(fn [db _]
(dissoc db :greeting)))
The following code displays 5 different emojis at the bottom of the screen, and 1 emoji in the upper center. I am trying to make it so that when one of the emojis on the bottom is clicked, that same emoji appears at the top center. I am able to update the atom that contains the emoji history with :on-click, but the image does not update with the current url.
(def emoji-history
(atom {:current "img/sad-tears.png"}))
(defn Img40 [src reaction]
[:img {:src src
:style {:width "60px"
:padding-right "20px"}
:on-click #(do
(js/console.log (get #emoji-history :current))
(swap! emoji-history assoc :current src)
(js/console.log (get #emoji-history :current)))}])
(defn CurrentEmoji []
[:img {:style {:width 40 :margin-top 15}
:src (get #emoji-history :current)}])
(defn EmojiDisplay []
[:div {:style {:text-align "center"}}
[CurrentEmoji]
[:div {:style {:text-align "center"
:margin-top "200px"
:padding-left "20px"}}
[Img40 "img/smile.png" "happy"]
[Img40 "img/sad-tears.png" "sad"]
[Img40 "img/happy-tears.png" "amused"]
[Img40 "img/surprised.png" "surprised"]
[Img40 "img/angry.png" "angry"]]])
Refer to the reagent.core namespace and use a Reagent atom like this:
(ns my-name.space.etc
(:require [reagent.core :as r]))
(def emoji-history
(r/atom {:current "img/sad-tears.png"}))
The line of code that you have here...
(swap! emoji-history assoc :current src)
...where you swap! your atom's value, that is correct :-)
Unlike a plain old Clojure atom, when a Reagent atom's value (state) is changed, a re-render of the UI is triggered.
Rarely is the entire UI re-rendered, though. Because Reagent wraps React, the React system will work out the minimal required changes to the DOM, so it's pretty efficient.
I love re-frame, but I realize that I'm having a bit of trouble finding a nice pattern for handling AJAX responses.
My situation is the following:
I have a "global" event handler that triggers some AJAX call and dispatches to some other global event handler, depending on whether that call was successful, e.g.:
(reg-event-db :store-value
(fn [db [_ value]]
(do-ajax-store value
:on-success #(dispatch [:store-value-success %])
:on-error #(dispatch [:store-value-error %])
db))
(reg-event-db :store-value-success
(fn [db [_ result]]
(assoc db :foobar result)))
(reg-event-db :store-value-error
(fn [db [_ error]]
(assoc db :foobar nil
:last-error error)))
(I am aware of reg-event-fx and stuff, I'm just avoiding it here for the sake of brevity and because I think it does not make any difference for my problem).
I also have (multiple, distinct) UI components that might trigger the :store-value event, like so:
(defn button []
(let [processing? (reagent/atom false)]
(fn button-render []
[:button {:class (when #processing? "processing")
:on-click (fn []
(reset! processing? true)
(dispatch [:store-value 123]))}])))
So in this case the component has local state (processing?) that is supposed to depend on whether the AJAX call is still in progress or not.
Now, what is the proper pattern here to have the button component react to the events :store-value-success and :store-value-error in order to reset the processing? flag back to false after the AJAX call has finished?
Currently, I'm working around that problem by passing down callbacks but that seems really ugly because it clutters the event handlers' code with stuff that does not really belong there.
The best solution that I've thought of would be to have the button component being able to hook into the :store-value-success and :store-value-error events and install its own handler for those events, like this:
(defn button []
(let [processing? (reagent/atom false)]
(reg-event-db :store-value-success
(fn [db _]
(reset! processing? false)))
(reg-event-db :store-value-error
(fn [db _]
(reset! processing? false)))
(fn button-render []
[:button {:class (when #processing? "processing")
:on-click (fn []
(reset! processing? true)
(dispatch [:store-value 123]))}])))
However, that does not work. As it seems, re-frame does not allow multiple event handlers per event. Instead, a subsequent invocation of reg-event-db on one single event id will replace the previous event handler.
So how do you guys handle situations like this?
I think reg-event-fx (src) might indeed help solve your problem.
You could add a subscription that watches app-state e.g.
(rf/reg-sub
:app-state
(fn [db]
(get db :app-state)))
and add this to your button, perhaps with a state function e.g.
(defn btn-state [state]
(if (= state :processing)
"processing"))
And then in the AJAX handler, you could add an fx to update the state-
(reg-event-fx ;; -fx registration, not -db registration
:ajax-success
(fn [{:keys [db]} [_ result]]
{:db (assoc db :app-state :default)
:dispatch [:store-value-success result]}))
(reg-event-fx ;; -fx registration, not -db registration
:ajax-error
(fn [{:keys [db]} [_ result]]
{:db (assoc db :app-state :default)
:dispatch [:store-value-error result]}))
and update the AJAX handler
(reg-event-db :store-value
(fn [db [_ value]]
(do-ajax-store value
:on-success #(dispatch [:ajax-success %])
:on-error #(dispatch [:ajax-error %])
db))
This would be one way to handle it via -fx. I think you have already started to see the need for tracking app state, and I think bumping it up into the subscriptions would help with complexity, at which point your button render is greatly simplified.
(defn button []
[:button {:class (btn-state #app-state)
:on-click (dispatch [:store-value 123]))}])))
As others have mentioned, I would recommend to use http-fx and make processing? part of your global state. The code would look like this:
Events:
(reg-event-fx
:request
(fn [{:keys [db]} [_ method url data]]
{:http-xhrio {:method method
:uri url
:params data
:format (ajax/json-request-format)
:response-format (ajax/json-response-format {:keywords? true})
:on-success [:success-response method url]
:on-failure [:error-response method url]}
:db (assoc db :processing? true)}))
(reg-event-db
:success-response
(fn [db [_ method url result]]
(assoc db :foobar response
:processing? false)}))
(reg-event-db
:error
(fn [db [_ method url result]]
(assoc db :foobar nil
:last-error result
:processing? false)}))
Subscriptions:
(reg-sub
:processing
(fn [db _]
(:processing? db)))
View:
(defn button []
(let [processing? #(rf/subscribe [:processing])]
[:button {:class (when processing? "processing")
:on-click #(dispatch [:store-value 123]))}])))
Hint: You could reuse this code with all your requests.
I am trying to get into ClojureScript and Om. There is a specific case which has me running in circles.
I have a component that is first rendered without a key.
(defn model-view [data owner]
(reify
om/IWillMount
(will-mount [_]
(om/transact! data [:stats] (fn [] {}))
(go
(let [response ((<! (api/get-stats (data :id))) :body)
stats (:stats response)]
(om/update! data [:stats] stats))))
om/IRender
(render [_]
(dom/div nil
(dom/h3 nil (data :title))
;; Here I want to use the :stats key in data that I
;; queried for in IWillMount, but its not present
;; the first time this model is rendered. It's only present
;; AFTER IWillMount has ran.
(om/build model-stats-view (data :stats)))))
The first time this component is called, the :stats key is simply not present in data. That's why I do an API call to get its stats. But React still calls the render function, thus the component crashes.
How can I set an initial state in this component that gives data an empty map called :stats, thus preventing trying to render nil in the (om/build model-stats-view) call?
I prefer to do all of my initialization in init-state, then access it in render-state. And I put a go-loop in my did-mount. When you update your init-state (i.e. :e-map) in the go-loop, it forces a call to render/re-render of the component. I use this in all of my components for inter-component/intra-component messaging. Just push something into a pub/sub channel and we are off to the races.
;To update my state I use a function:
(defn set-owner-state! [owner old-map-key old-map new-map]
(om/set-state! owner {old-map-key (merge old-map new-map)}))
om/IInitState
(init-state [_]
(println "queue->init-state")
{:e-map {:active-fsm nil}})
om/IDidMount
(did-mount [_]
(go-loop []
(let [[v _] (alts! [server-fsm-events dispatcher-events])
current-state (om/get-state owner)
e-map (:e-map current-state)]
; what goes in here is the logic to respond to a message
; in my case I have a match, it could be a cond or a set of
; if's.
(set-owner-state! owner :e-map e-map {:active-fsm :active :task-paths nil})
...
om/IRenderState
(render-state [_ {:keys [e-map]}]
(println "e-map:" e-map)
...
here's what i'd like to do:
I've got a ref that represents a list of items. I'd like to have a listbox (seesaw?) that displays this lists contents, updating automatically (whenever i change the ref).
You can use add-watch to add callback which will be called every time ref is modified. This callback should call method that updates listbox:
(def data (ref [1 2 3]))
(defn list-model
"Create list model based on collection"
[items]
(let [model (javax.swing.DefaultListModel.)]
(doseq [item items] (.addElement model item))
model))
(def listbox (seesaw.core/listbox :model []))
(add-watch data nil
(fn [_ _ _ items] (.setModel listbox (list-model items))))
Another way to do it is to bind the contents of the ref to the model of the listbox, using seesaw.bind.
(require [seesaw core [bind :as b]])
(def lb (listbox))
(def r (ref []))
(b/bind r (b/property lb :model))
The seesaw.bind library is well worth exploring, IMHO. The API is well documented once you have some idea how it all fits together; this blog post is a nice introduction.