-
Notifications
You must be signed in to change notification settings - Fork 20
Component & om.next shared
Using Stuart Sierra’s Component library worked well for us on the server. Having the entire system in a map passed to our endpoints and om.next/parser
functions (read
& mutate
) made our code well structured. It also made it easy to create fake implementations for our external dependencies, which made it easy to create a demo version of our app and open source it.
We took the same concept to our client, sticking the entire “client system map” or “client components” in the om.next reconciler’s :shared
map. This gave us the same power to select which version components to use at dev/demo/production time. The :shared
map is passed to each ui component and it’s also available for the om.next/parser
functions.
We didn't use use the Component library for our :shared
map. We created our own small library which also has the property of instantiate the components lazily (whenever they are used). This works well with ClojureScript’s module loading system, because only the namespaces using the components in the :shared
map will reference the component’s namespace, and ClojureScript will try to move these components into the smallest possible module.
See implementation:
eponai.common.shared
All the components created by this namespace are cached and meant to be used as singletons.
Here we're defining a UI component, getting a component and using the component's namespace to call a method on that component.
(ns eponai.web.ui_component
(:require
[om.next :as om :refer [defui]]
[om.dom :as dom]
;; Requiring the shared namespace so we can access the components.
[eponai.common.shared :as shared]
[eponai.web.login :as login]))
(defui UiComponent
Object
(render [this]
;; Getting the login component by its unique key :shared/login.
;; The first argument to shared/by-key can either be a component or a reconciler.
(let [login-component (shared/by-key this :shared/login)]
(dom/div nil
(dom/button #js {:onClick #(login/prompt-login! login-component)}
"Login!")))))
To create a shared component, one implements a multimethod defined in the eponai.common.shared
namespace, which is passed the reconciler
, a unique key to identify the component by, and which version of the component to get.
Code for a new component:
(ns eponai.web.hello_component
(:require
[eponai.common.shared :as shared]
;; Include other interesting components if the component needs them.
[eponai.web.firebase :as firebase]))
(defprotocol ISayHello
(say-hello [this language] "Returns a version of Hello in the specified language"))
(defmethod shared/shared-component [:shared/hello-component :env/dev]
[reconciler k env]
(reify ISayHello
(say-hello [_ _]
;; Ignores the language, just returns a default string.
"Hi dev")))
(defmethod shared/shared-component [:shared/hello-component :env/prod]
[reconciler k env]
;; We're passed the reconciler to be able to use any state or other shared component
;; If we need to.
(let [firebase (shared/by-key reconciler :shared/firebase)]
(reify ISayHello
(say-hello [_ language]
;; Here we're tracking every call to say-hello in firebase as well.
(firebase/update-in firebase
(firebase/path :demo/say-hello {:language language})
inc)
(condp = language
::swedish "Hej"
::english "Hi"
"Yo")))))
If you're using figwheel and you want to be able to reset components to their initial state on reload, call the
eponai.common.shared/clear-components!
function. This will reset the component cahce and return a new component when the eponai.common.shared/by-key
is called.