-
-
Notifications
You must be signed in to change notification settings - Fork 714
Using Stateful JS Components
You know what's good for you. You know what's right. But it doesn't matter - the wickedness of the temptation is too much.
The JS world is full of shiny component baubles: D3, Google Maps, Chosen, etc.
Of course, they are salaciously stateful and mutative. And, you, raised in a strictly functional home, with a stern immutable father, know they are wrong. But, my, how you still yearn for the sweet thrill of that forbidden fruit.
I won't tell if you don't. But careful plans must be made ...
If you want to use a stateful js component, you'll need to write two Reagent components:
- The outer component is responsible for sourcing data via a subscription or r/atom or cursor, etc.
- The inner function is responsible for actually wrapping and manipulating the stateful JS component via lifecycle functions.
Crucially the outer function (which sources data) then supplies that data through to the inner, via props.
(defn gmap-inner []
(let [gmap (atom nil)
options (clj->js {"zoom" 9})
update (fn [comp]
(let [{:keys [latitude longitude]} (reagent/props comp)
latlng (js/google.maps.LatLng. latitude longitude)]
(.setPosition (:marker @gmap) latlng)
(.panTo (:map @gmap) latlng)))]
(reagent/create-class
{:reagent-render (fn []
[:div
[:h4 "Map"]
[:div#map-canvas {:style {:height "400px"}}]])
:component-did-mount (fn [comp]
(let [canvas (.getElementById js/document "map-canvas")
gm (js/google.maps.Map. canvas options)
marker (js/google.maps.Marker. (clj->js {:map gm :title "Drone"}))]
(reset! gmap {:map gm :marker marker}))
(update comp))
:component-did-update update
:display-name "gmap-inner"})))
(defn gmap-outer []
(let [pos (subscribe [:current-position])] ;; obtain the data
(fn []
[gmap-inner @pos])))
Notes:
-
gmap-outer
obtains data via a subscription. It is quite simple. Trivial almost. - it then passes this data as a prop to
gmap-inner
. This inner layer has the job of wrapping/managing the stateful js component (Gmap in our case above) - when the data (delivered by the subscription) to the outer layer changes, the inner layer,
gmap-inner
, will be given a new prop -@pos
in the case above. - when the inner layer is given new props, the entire set of lifecycle functions defined for it will be engaged.
- the renderer for the inner layer ALWAYS renders the same, minimal container hiccup for the component. Even though the
props
have changed, the same hiccup is output. So it will appear to React as if nothing changes from one render to the next. The HTML is the same each time. No work to be done. It will leave the DOM untouched. - but this inner layer has other lifecycle functions. This is where the real work is done.
- for example, after the renderer is called (which ignores its props),
component-did-update
will be called. In this function, we have a chance to update the stateful JS component using the contents of the new props.
This example was developed by @jhchabran in this gist: https://gist.github.com/jhchabran/e09883c3bc1b703a224d#file-2_google_map-cljs
Deprecated Tutorials:
Reagent: