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
zackteo 2021-06-14T14:53:32.114300Z

Hello, I'm looking to manipulate a leaflet map from within re-frame, my immediate thoughts is that the map needs to be accessible, meaning its context will be part of my re-frame db. Are there any resources I can look at to better understand how to do this? I feel it is a bit confusing because I need to be able to access the leaflet context, and it seems like the javascript way is to use a react hook https://react-leaflet.js.org/docs/core-api#useleafletcontext . Sorry that my question is very leaflet specific, not sure where and how else to ask it. I am able to display the map already - just that I need to be able to dynamically add and remove points on the map etc

2021-06-15T15:58:44.133300Z

@zackteo, we have found that a functional reactive approach can be very effective when dealing with mutable JS view objects like a Leaflet object: 1. Model all aspects of your Leaflet map as immutable data in your db. If you want to have multiple maps on the page, your db model has to support multiple model instances. 2. Bind your Leaflet object to the relevant dom node when the view component is created 3. Produce a subscription to your map's immutable data 4. Create a reagent reaction that updates the JS Leaflet object with the immutable data as input. There are two important restrictions: 1) your immutable data is your only input to the reaction 2) the only side-effects of the reaction are changes to your mutable JS object. Your Leaflet object effectively becomes a materialized view of your app db. The only way to make changes to your mutable Leaflet object is by transforming the immutable data in the app db. Reagent/re-frame will take care of keeping your mutable JS in sync with your app db.

zackteo 2021-06-16T05:29:22.135500Z

@zalky did you use leaflet directly or react-leaflet ?

2021-06-16T12:55:23.135700Z

@zackteo, started with leaflet, eventually migrated to react-leaflet.

zackteo 2021-06-16T12:56:46.135900Z

Oo, okay okay - I was wondering if the reverse might be needed but i guess react-leaflet is actually more than flexible

zackteo 2021-06-16T12:57:53.136100Z

@zalky btw, by any chance do you have an example of this approach? If not no worries, I'll try to figure it out along the way 🙂

2021-06-16T13:07:42.136300Z

@zackteo, unfortunately I can't share the code, but there are not too many moving parts. Mostly using [:> Component props children ...] operators plus the update approach described above. I think as we moved to react-leaflet, we moved some of the leaflet mutations out of the update reaction, and just passed them as simple props to the react component (zoom, center, stuff like that). Maybe you've already seen this, but if you haven't, here is a very common pattern that is effectively the same thing as using an update reaction: https://github.com/day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md

2021-06-16T13:13:28.136700Z

Well, it's not exactly the same: with this approach, you do have to option of make your reactive update a function of two states. Potentially with this approach you can be more efficient and refined in how you update your mutable JS object, but it can be more complicated to reason about, and depending on your implementation, you can potentially lose some nice properties, such as time travel.

zackteo 2021-06-16T13:19:39.136900Z

Thanks for your help!! 😄

2021-06-16T13:19:52.137100Z

No problem, good luck!

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))

zackteo 2021-06-22T03:15:27.203400Z

@p-himik Strange, am certain my geoJSON is correct - but I do have this weird situation where the app won't "compile" until I save. But anyhow, I got it working! 🙂 https://github.com/zackteo/leaflet-example/blob/master/src/leaflet_example/core.cljs I do however have some questions, 1. ^{:key @state} , how might I know I would need to do something like this? Is there documentation that I can look at ? o: 2. I tried using reset! to change my geoJSON from a small square (geojson) on the left to a giant triangle on the right (other-geojson), I don't quite understand why the map will only re-render if I first change it to nil is this something to do with what triggers the re-render?

p-himik 2021-06-22T04:47:36.203900Z

Regarding GeoJSON being incorrect - it very well may be that it were simply my own experiments that were incorrect. And during my prior experience with GeoJSON, I always used feature and feature collections as the top-level objects. But seems like the spec allows having the geometry at the top level as well, so all is good. 1. When you use the same @state in a :div and in some component, and the latter is updated but the former is not. It means that the component has some issues in its state management or it simply disallows changing at least some of its state for some reason. Providing the :key metadata (or via a regular attribute, if the component itself supports it, like all the plain HTML entities) makes sure that when that key is different, the whole component is re-rendered. One thing to note - that key is stringified when it's used. 2. I blame it on the GeoJSON component's implementation. It has nothing to do with Reagent.

zackteo 2021-06-22T04:56:58.204100Z

Is the :key metadata is a HTML thing? or is that regular attribute ? Is there a link I can read up on this? :o I guess there's why in some example they wrap react-leaflet with some of their own structure https://github.com/instedd/planwise/blob/f7fde4e1d17c7aaba82357de7b8a8b1492440ec0/client/src/leaflet/core.cljs

zackteo 2021-06-22T05:10:46.204500Z

Right there's this https://github.com/reagent-project/reagent/blob/c214466bbcf099eafdfe28ff7cb91f99670a8433/doc/FAQ/ForcingComponentRecreation.md . But what is a regular attribute ? :o

p-himik 2021-06-22T05:13:20.204900Z

It's a React thing. Despite most of the documentation mentioning lists of all sorts, it's applicable outside of lists as well. • https://reactjs.org/docs/reconciliation.htmlhttps://reactjs.org/docs/lists-and-keys.html Planwise doesn't use react-leaflet. It's the problem with react-leaflet, not with leaflet itself. There are two ways to use :key in Reagent:

^{:key something} [:div {:class ...} ...]
and
[:div {:key something, :class ...} ...]
I'm not 100% sure whether the latter works if the component doesn't handle :key explicitly. I just stick to the metadata way at all times.

zackteo 2021-06-22T06:16:31.206Z

Okay! Thanks for all your help!! 😄 Appreciate it

1👍
zackteo 2021-06-14T14:55:06.115400Z

and it is abit confusing if i should use the components in react-leaflet or go to leaflet in the map creation

p-himik 2021-06-14T15:07:56.115500Z

This is pretty much a Reagent question and not a re-frame one because your main concern is not how to use app-db here but rather how to drive a JS library using CLJS data. Reagent has a few relevant examples and a documentation page about interacting with React components.

oliver 2021-06-14T15:39:04.115800Z

Hi, I have written a self-contained https://energiewende-rechner.org/][Re-Frame that I would like to complement with some backend functionality.  Specifically, I'd like to render certain components of the app as preview images on the server. These images should be different for different app states. I have implemented a basic backend in https://macchiato-framework.github.io , which seems to be the ideal choice. So far it works nicely: I can initalize the app on every request, load a state and then render HTML/SVG via `reagent.dom.server/render-to-string`. Here's what I'm uncomfortable with: AFAIK the Re-frame DB is always global and would be shared among multliple requests-handlings – I expect problems as soon as I get multiple, concurrent requests, where request A needs to render a component in state α while Request B must render it in state β. Is there a way to use separate db atoms for every request or any other way to ensure a certain state when invoking `reagent.dom.server/render-to-string`?

oliver 2021-06-15T07:20:50.122200Z

Good summary of the problem… and interesting ideas. The project at hand being a hobby I'm under no pressure to make this work; just found it to be a good occasion to take a first stab at SSR. As I've said: I've already gotten this to work: 1. Endpoint receives a request containing app state (as b64 query-param) 2. Set Re-frame to that state (via multiple dispatches) 3. Render SVG-component via reagent.dom.server/render-to-string 4. Convert SVG to PNG and save to disk 5. Redirect to the static PNG (with 302) – works with crawlers. Steps 2–4 are skipped if an image for that particular state already exists. My only remaining worry is about concurret requests: What if another request comes in during 2. Could the DB-Atom be reset by handler B dipatching against it while handler A is still building the state it wants to render. Whith a global DB atom I smell trouble, but this is hard to test – maybe there would be no issue. With my current project there wouldn't be, since I get a couple of hunded reuqests per day at best – but in principle? If I ran this on an actual server rather than a shared hosting limited to a single node runtime I could just add another node build, that starts no server at all but only takes the encoded state and spits the PNG to disk. The handlers would then just shell out, run that node script and then redirect to the resulting PNG. This would separate the Re-frame instances – but, again, I'd like to find something more elegant, even if it's less brutal than firing up Puppeteer all the time.

p-himik 2021-06-15T10:40:40.128800Z

Re-frame acts on a queue - no handlers are run in threads. So no handlers can run in parallel. However, workflows can run concurrently if they consist of multiple events - when handling an event results in dispatching another event via some means.

p-himik 2021-06-14T15:42:37.115900Z

Regarding render-to-string - why not just use [:img ...] and serve its content via a URL? Regarding state - no, but you can provide a separate key to each event handler that creates a request. Should go well with the built-in path interceptor.

oliver 2021-06-14T15:57:47.116100Z

Thanks for taking the time! 1. The handlers are actually supposed to generate the images. I want to render an svg-component with a certain state an save it into a static png. That png will then simply be served by nginx/apache 2. If I understand correctly your suggestion rests on the premise that the handlers are queried from my frontend code. I doubt this is possible, because the links to the endpoint have to be part of the static html (og:image meta-tag Social Media previews)

oliver 2021-06-14T15:58:06.116300Z

If I'm wrong about 2, I'd be glad if you could clarify,

p-himik 2021-06-14T16:04:11.116600Z

(reg-event-fx :make-request
  (fn [db [_ key &amp; params]]
    {:http-xhrio {...
                  :on-success [:on-success key]
                  :on-failure [:on-failure key]}}))

(reg-event-db :on-success
  (fn [db [_ key result]]
    (assoc db key result)))

(reg-event-db :on-failure
  (fn [db [_ key error]]
    ...))
Does it make it clearer what I meant?

p-himik 2021-06-14T16:04:28.116800Z

The crux is the key part.

p-himik 2021-06-14T16:06:02.117Z

> the links to the endpoint have to be part of the static html I don't see how it would prevent anything from doing anything. I don't see how it's relevant to app-db, but you can get the value of any &lt;meta&gt; tag in runtime.

oliver 2021-06-14T16:06:54.117200Z

I can (i.e. my frontend app can), but Facebook and Twitter may not…

oliver 2021-06-14T16:10:48.117400Z

Thanks again, for wrapping your head around this…to me it seems, that you intend your snippet to be frontend code. However, what I'd like is for any consumer (custom PHP, Facebook, Twitter, Google…) to send some app state (be it as JSON or a query param) to my endpoint and get as a response a screenshot of my app for that state).

p-himik 2021-06-14T16:13:09.117800Z

You have asked a question about re-frame, that's exactly why I'm indeed talking about the frontend part, given that re-frame is a frontend library. It can be used on backend, but it was not designed for that, IIRC. > However, what I'd like is for any consumer (custom PHP, Facebook, Twitter, Google…) to send some app state (be it as JSON or a query param) to my endpoint and get as a response a screenshot of my app for that state). So what does it have to do with re-frame?

oliver 2021-06-14T16:14:13.118Z

(I actually have that functionality working with a brute force approach: fire up a headless browser navigate to a local copy of the app, render, save a screenshot and serve that. But this is very heavy handed if I can find away to reuse my code on the server side.)

p-himik 2021-06-14T16:16:02.118200Z

OK, I think I get what you're doing now. app-db is global for an app instance. It's not shared between instances. You can simply open a new headless browser tab or window - it'll have its own app-db.

p-himik 2021-06-14T16:17:10.118400Z

I would definitely not use proper SSR for that. At least, not with re-frame.

oliver 2021-06-14T16:20:08.118600Z

Ok, that's an answer I can live with… it was just that it's already working… just that concurrency will probably become an issue as soon as I get more than one request at the same time.

oliver 2021-06-14T16:26:43.118900Z

Thanks again for your assessment… I'm not sure I'll give up just yet, but I see that this is not a solved problem (rendering inside a headless browser on every request still seems too brute-force to me if I can avoid it;)

2021-06-14T17:46:48.119200Z

I once read somewhere not to do a something like `:get-in`  subscription, but I can no longer find this information or what risks it involves

2021-06-14T17:49:12.119800Z

I'd like to use doxa with re-frame, but I'm not happy about writing all the subscriptions by hand and would like to just use the pull syntax, but I suspect this is analogous to :get-in subscription

p-himik 2021-06-14T17:53:59.120Z

FWIW, a headless browser is a much more reliable solution, given that your UI can potentially have custom JS and CSS.

p-himik 2021-06-14T17:55:12.120200Z

A generic get-in sub would make views know about the app-db structure. Some common pull functionality is not necessarily that. I consider it to be fine, as long as it has a very specific and limited scope.

p-himik 2021-06-14T17:56:28.120400Z

And this is the relevant documentation section: https://day8.github.io/re-frame/correcting-a-wrong/#a-final-faq

2021-06-14T18:14:57.120700Z

thanks, sounds reasonable

2021-06-14T18:16:50.120900Z

and that was the text I was looking for

oliver 2021-06-14T18:37:26.121500Z

With that I agree… there's four reasons why I's like to at least try the SSR approach: 1. Performance 2. easier content tweaking (Hiccup/svg) 3. Works in shared hosting (where I cannot start a headless browser) 4. Sheer fun and elegance… For now, I'll see how this works out… and hell, maybe I'll learn something about how Re-Frame/Reagent work under the hood and find a way to properly solve this.

1👍