This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-11-07
Channels
- # bangalore-clj (1)
- # beginners (255)
- # boot (29)
- # cider (16)
- # cljs-dev (13)
- # cljsrn (6)
- # clojure (200)
- # clojure-berlin (1)
- # clojure-dev (13)
- # clojure-dusseldorf (6)
- # clojure-greece (1)
- # clojure-india (1)
- # clojure-italy (1)
- # clojure-russia (33)
- # clojure-spec (28)
- # clojure-uk (27)
- # clojurescript (47)
- # cursive (32)
- # data-science (3)
- # datascript (1)
- # datomic (40)
- # emacs (39)
- # events (4)
- # fulcro (55)
- # graphql (16)
- # immutant (2)
- # luminus (2)
- # lumo (5)
- # off-topic (142)
- # onyx (50)
- # portkey (1)
- # re-frame (45)
- # reagent (80)
- # remote-jobs (2)
- # ring-swagger (3)
- # rum (9)
- # schema (3)
- # shadow-cljs (184)
- # spacemacs (3)
- # test-check (4)
- # unrepl (2)
- # yada (5)
Anyone about..? I have a weird issue… I have created a 3-form component in my Reagent code so that I can embed LeafletJS maps into other Reagent components. The problem is that the first time I use it it does not appear to have access to my app-state ratom. It feels as though there must be a weird order of precedence thing that I just don’t get here, but anyway… Can anyone help?
(if I (println …) the data that I want to see in the parent component, I see the data in the console. If I (println …) for the data that I want to see in the 3-form component on first render I don’t see it - I get a nil - and then thereafter I do see it, and my maps get their markers. )
(happy to paste into a gist, if there’s anyone that thinks that they might be able to help and so wants to see the code)
When you say “3-form” do you mean a form 3 component, that you create with reagent/create-class
?
If it’s not too many lines you could try the Slack “Code or Text Snippet” tool to post it
(defn re-leaflet
[mapname latitude longitude zoom-level markers]
(let [dn (r/atom nil)
instmap (r/atom nil)
mapdata {:foo "bar" :bam "baz"}
mn mapname
lt latitude
lg longitude
z zoom-level
producer-markers markers]
(println "markers next")
(println producer-markers)
(r/create-class
{:component-will-mount #(println "component-will-mount")
:component-did-mount (fn [ref]
(reset! dn (r/dom-node ref))
(let [map (js/L.map @dn)
mappositioned (-> map (.setView (array lt lg) z))]
(.addTo (js/L.tileLayer "http://{s}. mappositioned)
(doall
(for [producer-marker (get producer-markers mn)]
(-> (js/L.circle
(array
(:lat producer-marker)
(:long producer-marker))
(clj->js {:color (:color producer-marker)
:fillColor (:fillColor producer-marker)
:fillOpacity 0.5
:radius (:radius producer-marker)}))
(.addTo mappositioned)
(.bindPopup (:name producer-marker)))))
(def instmap mappositioned)))
:component-will-unmount (fn []
(r/unmount-component-at-node @dn))
:display-name (str "Leaflet Map - " mapname)
:reagent-render (fn [mapname latitude longitude zoom-level markers]
[:div {:style {:height 620}}
instmap])})))
Well, I can see one problem: you have a def
inside your component-did-mount
(def instmap mappositioned)
Er, ok… That works, in so far as the map gets drawn, the only thing that’s not working is the markers being “absent” on the first render
It might be related — you’re defining instmap
at the top, in the let
statement, but you’re also using def
to create a different symbol with the same name.
I’m thinking you meant (reset! instmap mappositioned)
there maybe?
Oh yeah, I need to pull that out of the let - using a ratom to hold the component gave massive errors in the console, that I wanted to eliminate. def works seamlessly.
What kind of errors were you getting?
react-dom.inc.js:17859 Uncaught Error: Objects are not valid as a React child (found: object with keys {options, _container, _leaflet_id, _containerId, _fadeAnimated, _panes, _paneRenderers, _mapPane, _controlCorners, _controlContainer, _onResize, _targets, _events, _handlers, _layers, _zoomBoundLayers, _sizeChanged, _initHooksCalled, zoomControl, attributionControl, boxZoom, doubleClickZoom, dragging, keyboard, scrollWheelZoom, touchZoom, _zoomAnimated, _proxy, _loaded, _zoom, _lastCenter, _size, _pixelOrigin, _firingCount, _layersMaxZoom, _layersMinZoom}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `Leaflet Map - ghana`.
at invariant (react-dom.inc.js:17859)
at traverseAllChildrenImpl (react-dom.inc.js:16578)
at traverseAllChildren (react-dom.inc.js:16606)
at Object.instantiateChildren (react-dom.inc.js:4254)
at ReactDOMComponent._reconcilerInstantiateChildren (react-dom.inc.js:10263)
at ReactDOMComponent.mountChildren (react-dom.inc.js:10302)
at ReactDOMComponent._createInitialChildren (react-dom.inc.js:6114)
at ReactDOMComponent.mountComponent (react-dom.inc.js:5933)
at Object.mountComponent (react-dom.inc.js:11409)
at ReactCompositeComponentWrapper.performInitialMount (react-dom.inc.js:4774)
Ah, ok, good that’s fixable
just tryin to remember how to fix it, lol
Basically what the error is telling you is that in your :reagent-render
function you’re creating a div
with a child of instmap
, which is an r/atom
, and React doesn’t know how to render CLJS atoms
You want to tell Reagent to convert that to something React can understand
I think it might be as simple as changing instmap
to [@instmap]
The @instmap
pulls the value out of the atom, and then putting it in square brackets tells Reagent to treat it like a DOM node
component.cljs:126 Error rendering component (in edge.dashboard_app.chains > edge.dashboard_app.re_leaflet > Leaflet Map - global)
reagent$impl$component$do_render @ component.cljs:126
(anonymous) @ component.cljs:143
reagent$ratom$in_context @ ratom.cljs:37
reagent$ratom$deref_capture @ ratom.cljs:43
reagent$ratom$run_in_reaction @ ratom.cljs:504
Leaflet Map - global_render @ component.cljs:143
(anonymous) @ react-dom.inc.js:5199
measureLifeCyclePerf @ react-dom.inc.js:4479
Ok, hang on a second, gotta look something up
you shouldn't render the Leaflet object at all
Sure… I put [@instmap] back to @instmap inside the :reagent-render function and the components start working again (apart from no markers on first render) and i get this again:
react-dom.inc.js:17859 Uncaught Error: Objects are not valid as a React child (found: object with keys {options, _container, _leaflet_id, _containerId, _fadeAnimated, _panes, _paneRenderers, _mapPane, _controlCorners, _controlContainer, _onResize, _targets, _events, _handlers, _layers, _zoomBoundLayers, _sizeChanged, _initHooksCalled, zoomControl, attributionControl, boxZoom, doubleClickZoom, dragging, keyboard, scrollWheelZoom, touchZoom, _zoomAnimated, _proxy, _loaded, _zoom, _lastCenter, _size, _pixelOrigin, _firingCount, _layersMaxZoom, _layersMinZoom, _renderer}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of `Leaflet Map - global`.
at invariant (react-dom.inc.js:17859)
at traverseAllChildrenImpl (react-dom.inc.js:16578)
at traverseAllChildren (react-dom.inc.js:16606)
at flattenChildren (react-dom.inc.js:15216)
at ReactDOMComponent._reconcilerUpdateChildren (react-dom.inc.js:10280)
at ReactDOMComponent._updateChildren (react-dom.inc.js:10388)
at ReactDOMComponent.updateChildren (react-dom.inc.js:10375)
at ReactDOMComponent._updateDOMChildren (react-dom.inc.js:6357)
at ReactDOMComponent.updateComponent (react-dom.inc.js:6171)
at ReactDOMComponent.receiveComponent (react-dom.inc.js:6133)
Doesn't make any sense - you have already rendered the :div that you give to Leaflet and which leaflet will modify
I have to run, here’s a blog post about Reagent lifecycles and embedding non-CLJS libraries that I was looking at. Might be useful or at least entertaining. http://timothypratley.blogspot.com/2017/01/reagent-deep-dive-part-2-lifecycle-of.html
Also, you might be interested in using react-leaflet, in most cases that makes using Leaflet easy: https://github.com/metosin/komponentit/blob/master/example-src/cljs/example/map.cljs
@manutter51 - Thanks, will have a read…
@juhoteperi - I’ll take a look at that… So what should be in my :reagent-render function in that code above if not the LeafletJS object?
@maleghast Just [:div]
@juhoteperi - Ok, that eliminates all the errors; thanks 🙂 Now, how is it that the first time I use the component it does not seem to be able to see my @app-state?
I “invoke” it inside another component, one that can__ see my @app-state and that tries to pass in the map of markers. More to the point at every other use of the component the markers appear
are the markers empty first, and then updated e.g. after some network request is done?
you component currently doesn't update Leaflet if markers update
Before I first use the component I get the app-data in my parent init function. The component that calls “re-leaflet” can see that app-state and I pass the map of markers into the call to re-leaflet
Here’s the console output:
Dashboard - Chains
core.cljs:182 Loading Dashboard Client data
core.cljs:182 markers next
core.cljs:182 nil
core.cljs:182 component-will-mount
core.cljs:182 Congratulations - your environment seems to be working
client.cljs:57 Reload websocket connected.
core.cljs:182 Loading Supply Chain data for ClientID - 1 and ChainID - 1
core.cljs:182 Loading Supply Chain Aspect data for ClientID - 1 and ChainID - 1 and Aspect Name - production
core.cljs:182 markers next
core.cljs:182 {ghana [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30}], brasil [{:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}], global [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30} {:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}]}
core.cljs:182 component-will-mount
core.cljs:182 markers next
core.cljs:182 {ghana [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30}], brasil [{:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}], global [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30} {:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}]}
core.cljs:182 component-will-mount
core.cljs:182 Loading Supply Chain data for ClientID - 1 and ChainID - 1
core.cljs:182 Loading Dashboard Client data
core.cljs:182 markers next
core.cljs:182 {ghana [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30}], brasil [{:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}], global [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30} {:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}]}
core.cljs:182 component-will-mount
core.cljs:182 markers next core.cljs:182 nil
so the first render markers are empty
Yeah, but the data is already in a ratom after core.cljs:182 Loading Dashboard Client data
No idea where that is printed, maybe when starting to load the data instead of when it is done?
Anyone, you will probably need to update :component-will-update
to handle markers updates
basically there is an init function that gets the data and then attempts to render a component. That component invokes this form-3 component as a child
After that it doesn't matter if the markers are empty at first
The tutorial I read (on the reframe wiki) about form-3 components said to not mess with the update lifecycle functions so I was avoiding any of that…
Re-frame tutorial doesn't talk about wrapping non-controlled elements into React lifecycle
I don't have examples on this now but you need :component-did-update (fn [this [_ prev-props]] ...)
and check if markers in (r/props this)
and :prev-props
changed
No that doesn't say anything about wrapping non-React component, it is only about creating native React components
I got this far using that information and a couple of pointers from people on the #clojure-uk channel
But as I said, you can use React-leaflet, and you don't need to care about lifecycle methods
I will go and look at that link in a sec - I didn’t want to throw away this work if I don’t have to…
simplest way to implement did-update would be to just remove all markers from Leaflet, and then add new ones, always
As React presumes props are provided as single object, but you use 5 arguments, you need to use both props and children to get new and old argument values, maybe something like this:
(fn [this [_ prev-mapname [prev-latitude prev-longitude prev-zoom-level prev-markers]]
(let [mapname (r/props this)
[latitude longitude zoom-level markers] (r/children r]
(when (not= prev-markers markers)
(.removeLayer leaflet ...)
(for [marker markers] ...))))
OK… I have no idea what you are suggesting here, which is probably because I have a very n00b (i.e. surface level only) understanding of React. I realise that this is not ideal, and under other circumstances I would want to immerse myself in React in order to really__ understand what Reagent is doing and so forth, but I am under a lot of time pressure… This is not me asking you to fix my code for me, I hasten to add. Are you saying that I can write a function that will effectively force the component to “look again” when the parent component passed in the map of markers and update itself?
It seems to have no problem(s) getting the other values from the parent component (the name, which is a string, and the lat, long and zoom which are all numbers), so why can it not “see” the map that I am passing in? The parent component can see it - I can (println …) the map in the parent component and the data is all there.
I think I see what’s happening…
Dashboard - Chains
core.cljs:182 Loading Dashboard Client data
core.cljs:182 markers being pushed into the re-leaflet component
core.cljs:182 nil
core.cljs:182 markers next
core.cljs:182 nil
core.cljs:182 component-will-mount
core.cljs:182 Congratulations - your environment seems to be working
client.cljs:57 Reload websocket connected.
core.cljs:182 markers being pushed into the re-leaflet component
core.cljs:182 {ghana [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30}], brasil [{:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}], global [{:name Producer One, :lat 7.188, :long -1.708, :radius 10000, :color green, :fillColor #0f3} {:name Producer Two, :lat 6.591, :long -1.437, :radius 10000, :color #ffaa00, :fillColor #f30} {:name Producer Three, :lat -4.39, :long -55.46, :radius 80000, :color green, :fillColor #0f3} {:name Producer Four, :lat -20.8, :long -49.35, :radius 80000, :color red, :fillColor #f03}]}
Right… So I need to update the component that I’ve written, because the first time that the parent component is eval-ed it doesn’t see the app-state either. OK, I will go and figure out how to do this update inside the form-3 component. Thanks for putting me on the path, @juhoteperi
@maleghast (I haven't followed the entire discussion but ..) is this the re-frame
docs you were looking for?
https://github.com/Day8/re-frame/blob/master/docs/Using-Stateful-JS-Components.md
@juhoteperi
that one should be helpful, and the last note is good: > the props passed (in this case @pos) in must be a map, otherwise (reagent/props comp) will return nil. Using map as props is going to be simpler than using several arguments
Thanks @mikethompson and @juhoteperi