I am going through this OM tutorial but it's not clear to me when to use OM components vs plain functions (in particular the om/component macro).
The tutorial writes:
The first argument is a function that takes the application state data
and the backing React component, here called owner. This function must
return an Om component - i.e. a model of the om/IRender interface,
like om.core/component macro generates
; here the function (fn [app owner]) indeed returns an OM component
(om/root
(fn [app owner]
(om/component (dom/h2 nil (:text app))))
app-state
{:target (. js/document (getElementById "app"))})
In the next section we find the following example of a rendering loop for a list:
; this one does not return an om component (or does it?). it returns a virtual dom
(om/root
(fn [app owner]
(apply dom/ul nil
(map (fn [text] (dom/li nil text)) (:list app))))
app-state
{:target (. js/document (getElementById "app0"))})
Here, we're basically just returning a (virtual) dom directly, not wrapped in an OM component, so the question would be: Why does the om/component macro exist? The macro simply helps us to reify the IRender function, but it appears that we can also just use plain functions for that. I would reify OM components that have lifecycle state (or need the owner to call get-props) but for components that just need to create virtual dom I'd rather go for simple functions (so I don't need the build/build-all functions to create my virtual dom). What am I missing here? Why is the macro still useful (and I don't see it).
I had this same question last week, and I dug through the Om source code to find out.
I couldn't find any functional difference between using the om/component macro and not. But maybe this info can shed some light on someone who knows more about React.
Any function f passed to om/root (and subsequently om/build) is placed inside of a container Om component. This Om component is just a dummy React component that forwards all life-cycle events to the result of f if it implements Om's lifecycle protocols (i.e. when it is a reify object).
If the result of f is not a reify object that implements those protocols, it is assumed to be a React component, and it is used as the value returned by the render lifecycle function.
(relevant: Om's render function here)
The om/component macro is just a shorthand for the defn and reify combination when you do not need to pass in state
Related
I am trying to use AWS Amplify Authentication lib in a re-frame app.
The lib provides a higher order component withAuthenticator which is supposed to wrap the main view of your app. I am trying to use reactify-component and adapt-react-class but unfortunately I get the following error:
Uncaught TypeError: Failed to construct ‘HTMLElement’: Please use the ‘new’ operator, this DOM object constructor cannot be called as a function.
(defn main-panel []
[:div
[:h1 "Hello" ]])
(def root-view
(reagent/adapt-react-class
(withAuthenticator
(reagent/reactify-component main-panel))))
(defn ^:dev/after-load mount-root []
(re-frame/clear-subscription-cache!)
(aws-config/configure)
(re-frame/dispatch-sync [::events/initialize-db])
(reagent/render [root-view]
(.getElementById js/document "app")))
Any help is appreciated
I had this issue with reagent + amplify.
Solved it with 2 changes, but I'm unsure if both are needed
#1 Change output of the google closure compiler to es6 (or higher). Amplify seems to use es6 features that Cannot be polyfilled.
This is for shadow-cljs (shadow-cljs.edn), but this should be possible for other build systems as well.
{:builds {:app {:compiler-options {:output-feature-set :es6}}}}
Disclaimer: I switched to shadow-cljs from lein-cljsbuild since I could not get lein-cljsbuild to respect the configuration for es6 output.
#2 Use functional components.
In reagent 1.0.0-alpha1 and up you can change the compiler to produce functional components by default.
(ns core
(:require
[reagent.core :as r]))
(def functional-compiler (r/create-compiler {:function-components true}))
(r/set-default-compiler! functional-compiler)
There are other ways to make functional components. Check the documentation if you don't like, or can't use, this approach.
I Javascript's version of react I can use
this.props
but what can I use to gave props in
:reagent-render
callback?
I am trying to do as done here in Chart.js line 14.
To answer your question, you can access the component and props in reagent-render by doing something like this
(ns chartly.core
(:require
[reagent.ratom :as ra]
[reagent.core :as r]))
(defn data-fn [implicit-this]
;; use implicit-this as needed, which is equivalent to "this"
;; From create-class docs -
;; :component-did-mount (fn [this])
)
(defn chart-render []
(let [comp (r/current-component) ;; Reagent method
r-props (r/props comp) ;; Reagent method
js-props (.-props (clj->js comp))]
(js/console.log "PROPS" r-props) ;;-- etc...
[:div {:style {:width 100}}]))
(def data (ra/atom {})) ;;--> see more info on reagent-atom
(defn chart-component []
(r/create-class
{:component-did-mount data-fn
:display-name "chart-component"
:reagent-render chart-render}))
To use -
[chart-component]
However, this is anti-pattern and will be quite difficult to manage, since you are trying to update data prop internally with component-did-mount, which, on completion, would need to manually signal the React component to update itself.
One of the features of Reagent is that is offers to detect changes and updating the component, usually using atom. See Managing State in Reagent for more info.
What you are looking to do is exactly what the Re-frame Framework is helping to manage. You set-up a data-store, and when the store changes, it signals to subscribers (React elements) to update accordingly, and you don't have to handle the signal changes yourself.
There are some edge cases where tapping into the lifecyle events are necessary, especially with charts and other drawing libraries, but I might recommend re-visiting if you find reagent atoms and/or the re-frame library insufficient for your needs. Hope this helps.
As far as I see, you accept some Hiccup data from a user as a string, right? And then try to evaluate it into user namespace, where only reagent library is loaded?
First, the more you build your further code to evaluate, the more difficult to understand it becomes. You could use something like this:
(binding [*ns* user-ns] (eval (read-string user-data)))
Also, to prevent wrong input, it would be better to validate user's input either with Schema or clojure.spac libraries. Since read-string returns a data structure, it might be checked with those two as well. So you would see an error before starting to evaluate something.
how implement $(document).ready(function(){}) in clojurescript.
I tried this:
(. ready js/document ());;but i am trying to achieve the callback function
But doesn't seem right to me. Any ideas?
new to clojurescript so i am bit confused as to how to do this.
This should work:
(.addEventListener
js/window
"DOMContentLoaded"
(fn [] (.log js/console "DOMContentLoaded callback")))
For simply a clojurescript entry point, you may implement a main function in e.g. the core namespace:
(ns app.core)
(defn main []
(activate-app))
Then call the entry point at the end of the module:
(main)
The idea is to have the entry point main function called after all code has been loaded. Hence the module with the entry point call should not itself be required by any other modules.
A variation sets up an entry point explicitly called from javascript after the compiled clojurescript has been loaded:
(defn ^:export main []
(activate-app))
(set! js/cljs-entry-point main)
This entry point can now be called from a script element at the bottom of the body of the associated html document:
<script>cljs_entry_point()</script>
A benefit with the latter approach is that other modules still can require the module containing the entry point.
Consider a function main which calls a number of other functions.
(defn main []
(app-instruction-1)
(app-instruction-2))
(set! (.-onload js/window) main)
How can I get the Clojurescript namespace I am in from within a clojurescript program? I want to do this do provide certain debug information (it only needs to work in dev mode)
Namespaces are not first class in ClojureScript as they are in Clojure. There's absolutely no way to get the namespace at runtime. It is possible to get this information at macroexpansion time if you're not afraid of accessing some the ClojureScript compiler internals. There should probably be an API for this - but we're not there yet.
You can get the name of the current namespace with this trick, which takes advantage of :: creating a namespaced symbol for you in the current namespace:
(namespace ::x)
You probably don't want to use that value for anything, because if the code is compiled the internal representation will change. If you want to live dangerously, then in the browser you can then access the js object that holds the namespace like this:
(reduce (fn [ns n] (aget ns n))
js/window
(clojure.string/split (namespace ::x) #"\."))
During macro-expansion you can access &env and retrieve namespace information from the :ns key like this:
(:ns &env)
(:name (:ns &env))
This only works at macro-expansion/compile time and not at runtime.
You can try this
(apply str (drop-last 2 (str `_)))
I'm integrating some ClojureScript code with a JS library call that takes a callback function. The JS library passes data to the callback using JavsScript's "this" keyword.
I can get it to work using (js* "this"). For example:
(libraryCall (fn [] (.log console (js* "this"))))
Is there a way to get at the "this" context from ClojureScript without resorting to js*?
Use the built-in this-as macro. It takes a name and a body, and evaluates the body with the name bound to JavaScript this.
e.g.
(libraryCall (fn [] (this-as my-this (.log js/console my-this))))
Great question... had to dig into the compiler code to find it, it's not well advertised at all.
I'll add it to the book.