I would just never have utility event handlers
the two cases for me would be user initiated dispatch and "external process" initiated dispatch
so ::user-clicked-submit-form and then, ::form-successfully-submitted. ::form-failed-to-submit
::user-clicked would be initiated by the user, the other two are initiated by the http goblin that lives outside of pure fp
if you need a utility form-save, write a function instead
(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)}))
OR
(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)))
dispatching should be tied to a real thing that happened
Nice. Thanks.
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. :)
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.
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]))
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.
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)
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
I can't answer that question because I have no idea what your current code is doing.
okay - maybe ill create a minimal example
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.
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.
A learning process should be gradual, not in the "let me tackle it all at once" kind of way.
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
> 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. :)
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
Please go through the Reagent examples. Everything is there. This is a very popular use-case, so it has been thoroughly documented and tested.
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
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.
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
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.
@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.
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
Will continue when I wake up tmr :x
I had to change your code a bit so it loads. Your GeoJSON is wrong.
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 ...]
.
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"}}
[:> TileLayer {:url "//{s}.<http://tile.openstreetmap.org/{z}/{x}/{y}.png%22|tile.openstreetmap.org/{z}/{x}/{y}.png">}]
^{:key state}
[:> GeoJSON
{:data (clj->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))