re-frame

https://github.com/Day8/re-frame/blob/master/docs/README.md https://github.com/Day8/re-frame/blob/master/docs/External-Resources.md
emccue 2021-06-21T00:34:56.188900Z

I would just never have utility event handlers

emccue 2021-06-21T00:35:22.189100Z

the two cases for me would be user initiated dispatch and "external process" initiated dispatch

emccue 2021-06-21T00:36:00.189300Z

so ::user-clicked-submit-form and then, ::form-successfully-submitted. ::form-failed-to-submit

emccue 2021-06-21T00:36:29.189500Z

::user-clicked would be initiated by the user, the other two are initiated by the http goblin that lives outside of pure fp

emccue 2021-06-21T00:36:52.189700Z

if you need a utility form-save, write a function instead

emccue 2021-06-21T00:38:10.189900Z

(defn form-save [db]
  [[:http-xhrio ...info...]]

(rf/reg-event-fx
  ::user-clicked-submit-form
  (fn [{:keys [db]} _]
    {:db (... set loading flag ...)
     :fx (form-save db)}))

emccue 2021-06-21T00:38:12.190100Z

OR

emccue 2021-06-21T00:39:33.190300Z

(defn form-save [db]
  {:db (... set loading flag ...)
   :fx [[:http-xhrio ...info...]]}

(rf/reg-event-fx
  ::user-clicked-submit-form
  (fn [{:keys [db]} _]
    (compose-stuff 
      (constantly {:db (..set something..)})
      form-save)))

emccue 2021-06-21T00:41:49.190600Z

dispatching should be tied to a real thing that happened

Oliver George 2021-06-21T01:09:34.190800Z

Nice. Thanks.

p-himik 2021-06-21T09:45:15.191600Z

I think it's worth specifying that the above "should" is a matter of opinion and not a prescription from the docs. At least, I don't remember the docs saying anything of the sorts. And, obviously, I prefer having util events myself. :)

p-himik 2021-06-21T09:48:33.191800Z

Reagent has reactions, a way to generate new cached state based on existing ratoms/reactions: https://github.com/reagent-project/reagent/blob/master/doc/ManagingState.md Regarding the "bind" part - attach a ref to the relevant React component, use that ref to get the DOM node and pass it to Leaflet. Reagent has some relevant and I would even say must-read examples in its repo on how to interact with React and JS components.

zackteo 2021-06-21T09:56:49.192100Z

I don't really understand (isn't this what re-frame subscriptions do already?) - do I just wrap my subscription like so (reagent.ratom/make-reaction @(rf/subscribe [:results]))

p-himik 2021-06-21T09:58:33.192300Z

Re-frame subscriptions are based on Reagent reactions. No need to wrap them. The only reason to wrap them is if you want to add some computation to them. But even then - it's better to have a proper subscription for it.

zackteo 2021-06-21T10:01:29.192500Z

hmmm, okay, in that case reagent.ratom/make-reaction isn't what I want right? Since I have re-frame I think my issue now is that I believe I am using a subscription correctly, but it is not re-rendering the component.(leaflet map)

zackteo 2021-06-21T10:02:59.192700Z

So to force my leaflet map to re-render, do I have to wrap my map component with reagent/create-class as in here? https://github.com/day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md

p-himik 2021-06-21T10:03:58.193100Z

I can't answer that question because I have no idea what your current code is doing.

zackteo 2021-06-21T10:04:37.193300Z

okay - maybe ill create a minimal example

p-himik 2021-06-21T10:05:19.193500Z

When you use reagent/create-class, you create what's called a form-3 component. They're useful for interacting with JS components, but are not truly necessary because we also have form-2 components and reagent.core/with-let which often can be used instead.

p-himik 2021-06-21T10:06:25.193700Z

Before you create a minimal example, I still urge you to go through the relevant Reagent examples. And write a simple Leaflet app without re-frame so that you aren't compounding points of failure.

p-himik 2021-06-21T10:06:51.193900Z

A learning process should be gradual, not in the "let me tackle it all at once" kind of way.

zackteo 2021-06-21T10:16:51.194100Z

Thanks for your help @p-himik. I actually have been using re-frame already so I don't think im compounding points of failure per se. It's just that the repo I'm working on is private cause it is my final year capstone project with a company. I already did a simple example in pure reagent to display a geojson area on my map. Whereupon I moved it into my initial re-frame db state and it still displays. So now the step I'm taking is to be able to click a button and change update that part of the db. Which does work. But it does not update the map

p-himik 2021-06-21T10:18:53.194300Z

> to be able to click a button and change update that part of the db > it does not update the map That should work in the Reagent app as well. If you haven't tried it there, then the example is too minimal. :)

zackteo 2021-06-21T10:18:59.194500Z

And I'm just trying to figure out how to make the map reactive in that sense. But the jump from normal components to having to create my own stateful component seems quite big. So I'm trying to understand how to best accomplish this re-rendering of the map

p-himik 2021-06-21T10:19:49.194700Z

Please go through the Reagent examples. Everything is there. This is a very popular use-case, so it has been thoroughly documented and tested.

zackteo 2021-06-21T10:22:36.194900Z

Okay I'll try doing so again! Tho my past attempts have been a bit confusing. Especially because I'm not exactly sure how to apply it to leaflet instead - I'll try coding it out

p-himik 2021-06-21T10:25:43.195100Z

After doing that, try creating such a Reagent example yourself, but for Leaflet and with a button that updates the state. No re-frame, only Reagent. If that works, then you're all set and it should be trivial to switch to re-frame after that. If that doesn't work, link that example here and I'll take a look.

zackteo 2021-06-21T10:28:36.195300Z

Alright! Thanks for your help! Honestly reagent/clojurescript stuff can come across as pretty daunting compared to clojure. Perhaps particularly because I have some slight js/react experience but not nearly enough, and none with setting things up from scratch

p-himik 2021-06-21T10:33:49.195500Z

Sure thing. Indeed. React by itself is enough for a novice to feel daunted. Adding Reagent with ClojureScript in top certainly doesn't help. :) That's why learning gradually is important.

2021-06-21T13:31:28.195900Z

@zackteo, I think p-himik provides some good advice. A strong understanding of Reagent will come in real handy building any kind of Re-frame app beyond simple examples. The only thing I'll add is to draw your attention to two important things about https://github.com/day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md example. 1. Notice that all the updates to the mutable javascript object (aside from the initial creation) happen in a single update fn in the :component-did-update lifecycle method. The question then is, where does this update fn get its data? The answer is you have to build out an immutable data model for your map in your re-frame app (in the example it would be single set of coordinates). Your event handlers update this immutable model in your db, and then your subscriptions ( :current-position in the example) get that immutable data to your update fn. create-react-class on its own doesn't help update your map, unless you close out the whole event handler -> immutable data -> subscription -> update fn loop. 2. Notice that the mutable JS object is isolated inside the view component gmap (atom nil). Because it is mutable state, it is tempting to try updating it directly via event/effect handlers. While this can work, you lose some important properties of your system, one of them being that mutations on your JS object produce view changes directly, and break the MVC isolation that Reagent/Re-frame try to impose. Instead, the https://github.com/day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md approach isolates your JS objects as part of the view, and makes them a function of the immutable data provided by your subscriptions. This is similar to how the stateful DOM structure is a function of the data from your subscriptions, and not something you manipulate in your event/effect handlers.

zackteo 2021-06-21T14:54:01.196300Z

https://github.com/zackteo/leaflet-example I couldn't figure out how to use make-reaction or reaction for now. I put just used an atom directly and that won't re-render the map. Am not sure what my train of thought should be on how to get my leaflet-map to re-render

zackteo 2021-06-21T14:55:56.196600Z

Will continue when I wake up tmr :x

p-himik 2021-06-21T16:56:28.196800Z

I had to change your code a bit so it loads. Your GeoJSON is wrong.

p-himik 2021-06-21T17:01:55.197Z

Apart from that, react-leaflet is not a good wrapper - it does not respect data changes of already rendered components. To fix that, add ^{:key @state} in front of [GeoJSON ...].

p-himik 2021-06-21T17:09:10.197200Z

Here's a simplified and fixed version of your code. I used a simpler GeoJSON feature just because it's easier to see with it that it works.

(ns leaflet-example.core
  (:require [reagent.core :as r]
            [reagent.dom :as d]
            ["react-leaflet" :refer [MapContainer TileLayer GeoJSON]]))

(def point [-104.99404 39.75621])
(def geojson {:type     "Feature"
              :geometry {:type        "Point"
                         :coordinates point}})

(defn leaflet-map [state]
  [:div
   [:> MapContainer
    {:center (reverse point) :zoom 11
     :style {:width "1000px" :height "1000px"}}
    [:&gt; TileLayer {:url "//{s}.<http://tile.openstreetmap.org/{z}/{x}/{y}.png%22|tile.openstreetmap.org/{z}/{x}/{y}.png">}]
    ^{:key state}
    [:&gt; GeoJSON
     {:data (clj-&gt;js state)}]]])

(defn app []
  (r/with-let [state (r/atom geojson)
               move-left (fn [state]
                           (update-in state [:geometry :coordinates 0]
                                      (fn [x]
                                        (- x 0.01))))
               move-left! #(swap! state move-left)]
    [:div
     [:button {:on-click move-left!}
      "Move point left"]
     [leaflet-map @state]]))

(defn mount-root []
  (d/render [app] (.getElementById js/document "app")))

(defn ^:export init []
  (mount-root))