Fork me on GitHub
#re-frame
<
2022-10-26
>
Lycheese13:10:57

Is there a way to watch a path in the app-db and when it changes dispatch an event with the new value? (e.g. to save it to localStorage )

p-himik13:10:20

You can use the built into re-frame on-changes interceptor factory function to create an interceptor that you can then register as a global one. Or you can write your own global interceptor if that makes more sense.

Lycheese13:10:45

Ah okay. So in my case I should write an interceptor that appends the local storage event to the effects map under :dispatch ? Looking at on-changes it should probably work like this?

(defn save-path-to-local-storage
  [path]
  (rf/->interceptor
   :id :save-path-to-local-storage
   :after (fn [context]
            (let [new-db   (rf/get-effect context :db)
                  old-db   (rf/get-coeffect context :db)
                  new-in      (get-in new-db path)
                  old-in      (get-in old-db path)
                  changed-in? (and (contains? (rf/get-effect context) :db)
                                   (not (identical? new-in old-in)))]
              (if changed-in?
                (rf/assoc-effect context :dispatch [:save-to-local-storage new-in])
                context)))))

p-himik13:10:28

Almost - assoc-effect will override any existing :dispatch. So instead you want to add another :dispatch vector to the :fx effect (if it exists - otherwise, just create it yourself). Something like (conj (or (rf/get-effect ctx :fx) []) [:dispatch [:save-to-local-storage new-in]]).

Lycheese15:10:05

Ah, I totally overlooked that. When looking for localStorage usage in re-frame I found that https://github.com/day8/re-frame/blob/ecb756e66fe30efca896879369a420df1e271605/examples/todomvc/src/todomvc/db.cljs#L61 uses ahttps://github.com/day8/re-frame/blob/ecb756e66fe30efca896879369a420df1e271605/examples/todomvc/src/todomvc/events.cljs#L68 to just directly write to localStorage without adding another event and effect in between. Since it is in the official example I guess that is the way it should be done? Is there a downside to have side-effecting code in an interceptor? New version:

(defn save-path-to-local-storage
  [path]
  (rf/->interceptor
   :id :save-path-to-local-storage
   :after (fn [ctx]
            (let [new-db   (rf/get-effect ctx :db)
                  old-db   (rf/get-coeffect ctx :db)
                  new-in      (get-in new-db path)
                  old-in      (get-in old-db path)
                  changed-in? (and (contains? (rf/get-effect ctx) :db)
                                   (not (identical? new-in old-in)))]
              (when changed-in?
                (.setItem js/localStorage :annotation (pr-str new-in)))
              ctx))))

p-himik15:10:35

That's exactly how the built-in do-fx interceptor makes that :fx work - by issuing side effects. But I myself prefer to keep side effecting points to a minimum.