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.
Related
I have a library of functions which I want to let users play with in the browser.
So I want to set up a situation like this :
I'm developing with figwheel and devcards.
In the main core.cljs I require various functions from my library, so they're all in scope.
Now I want to let the user enter some code which calls that library.
I see how I can run that code with eval, but I can't see how to make my library functions visible to the code being evaled.
And I'm confused by most of the documentation I'm seeing about this (eg. How can I make functions available to ClojureScript's eval?)
Is it possible? And if so, does anyone have a simple example of it being done?
cheers
Phil
Yes, it is possible to provide access to an ambient / pre-compiled library used by evaluated code.
First, you must ensure that the functions in your library are available in the JavaScript runtime. In other words, avoid :advanced optimization, as this will eliminate functions not called at compile time (DCE). Self-hosted ClojureScript is compatible with :simple.
Second, you need to make the analysis metadata available to the self-hosted compiler that will be running in the browser (either making use of cljs.js/load-analysis-cache! or an optional argument to cljs.js/empty-state).
A minimal project illustrating how to do this is below (and also at https://github.com/mfikes/ambient):
Project Code
src/main/core.cljs:
(ns main.core
(:require-macros [main.core :refer [analyzer-state]])
(:require [cljs.js]
[library.core]))
(def state (cljs.js/empty-state))
(defn evaluate [source cb]
(cljs.js/eval-str state source nil {:eval cljs.js/js-eval :context :expr} cb))
(defn load-library-analysis-cache! []
(cljs.js/load-analysis-cache! state 'library.core (analyzer-state 'library.core))
nil)
src/main.core.clj:
(ns main.core
(:require [cljs.env :as env]))
(defmacro analyzer-state [[_ ns-sym]]
`'~(get-in #env/*compiler* [:cljs.analyzer/namespaces ns-sym]))
src/library/core.cljs:
(ns library.core)
(defn my-inc [x]
(inc x))
Usage
We have a main.core namespace which provides an evaluate function, and this example will show how to call functions in an ambient / pre-compiled library.core namespace.
First, start up a browser REPL via
clj -m cljs.main
At the REPL, load our main namespace by evaluating
(require 'main.core)
Test that we can evaluate some code:
(main.core/evaluate "(+ 2 3)" prn)
This should print
{:ns cljs.user, :value 5}
Now, since main.core required library.core, we can call functions in that namespace. Evaluating this at the REPL will yield 11:
(library.core/my-inc 10)
Now, let's try to use this "ambient" function from self-hosted ClojureScript:
(main.core/evaluate "(library.core/my-inc 10)" prn)
You will see the following
WARNING: No such namespace: library.core, could not locate library/core.cljs, library/core.cljc, or JavaScript source providing "library.core" at line 1
WARNING: Use of undeclared Var library.core/my-inc at line 1
{:ns cljs.user, :value 11}
In short, what is going on is that even though library.core.my_inc is available in the JavaScript environment, and can indeed be called, producing the correct answer, you get warnings from the self-hosted compiler that it knows nothing about this namespace.
This is because the compiler analysis metadata is not in the main.core/state atom. (The self-hosted compiler has its own analysis state, held in that atom in the JavaScript environment, which is separate from the JVM compiler analysis state, held via Clojure in the Java environment.)
Note: If we instead had the source for library.core compiled by the self-hosted compiler (by perhaps by using main.core/evaluate to eval "(require 'library.core)", along with properly defining a cljs.js/*load-fn* that could retrieve this source, things would be good, and the compiler analysis metadata would be in main.core/state. But this example is about calling ambient / pre-compiled functions in library.core.
We can fix this by making use of cljs.js/load-analysis-cache! to load the analysis cache associated with the library.core namespace.
This example code embeds this analysis cache directly in the code by employing a macro that snatches the analysis cache from the JVM-based compiler. You can transport this analysis cache to the browser by any mechanism you desire; this just illustrates one way of simply embedding it directly in the shipping code (it's just data).
Go ahead and evaluate the following, just to see what the analysis cache for that namespace looks like:
(main.core/analyzer-state 'library.core)
If you call
(main.core/load-library-analysis-cache!)
this analysis cache will be loaded for use by the self-hosted compiler.
Now if you evaluate
(main.core/evaluate "(library.core/my-inc 10)" prn)
you won't see any warnings and this will be printed:
{:ns cljs.user, :value 11}
Furthermore, since the self-hosted compiler now has the analysis metadata for libraray.core, it can properly warn on arity errors, for example
(main.core/evaluate "(library.core/my-inc 10 12)" prn)
will cause this to be printed:
WARNING: Wrong number of args (2) passed to library.core/my-inc at line 1
The above illustrates what happens when you don't have the analyzer cache present for a namespace and how to fix it using cljs.js/load-analysis-cache!. If you know that you will always want to load the cache upon startup, you can simply things, making use of an optional argument to cljs.js/empty-state to load this cache at initialization time:
(defn init-state [state]
(assoc-in state [:cljs.analyzer/namespaces 'library.core]
(analyzer-state 'library.core)))
(def state (cljs.js/empty-state init-state))
Other Projects
A few (more elaborate) projects that make library functions available to self-hosted ClojureScript in the browser:
Klangmeister
power-turtle
life-demo
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)
Is it possible to use arbitrary node.js modules in a clojurescript project? If yes, how do I go about including them? If not, why not?
Yes, you can, there is nothing special about it:
(def fs (js/require "fs"))
(println (.readdirSync fs js/__dirname))
Be careful with the externs if you don't use optimizations none.
Edit: Does leiningen play with the various js package managers?:
Nope. Since the language does not have packages, it cannot know. You have to do js dependency management and lein deps too. There is a lein-npm and a lein-bower to help with integrating these two package managers.
Yes, since late 2017. With shadow-cljs or Lumo now it's not problem to import npm modules in ClojureScript code anymore.
(ns app.main
(:require ["dayjs" :as dayjs]
["shortid" :as shortid]
["lodash" :as lodash]
["lodash" :refer [isString]]))
Read this topic for details: Guide on how to use/import npm modules/packages in ClojureScript?
Since ClojureScript 1.9.854, there's better support to declare npm modules as dependencies and to require them from your namespaces.
In order to declare it as a dependency, you need to use the :npm-deps compiler option (together with the :install-deps one, if you want lein/bootto automatically install it).
:npm-deps is a map from keyword to string, where the keyword is the name of the dependency you would use to install it using npm and the string is the version of the dependency.
An example of what you could add to your project.clj (if you use lein-cljsbuild), in order to use left-pad:
:cljsbuild {:builds [{:id "prod"
:source-paths ["src"]
:compiler {:main left-pad-demo.core
:output-to "package/index.js"
:target :nodejs
:output-dir "target"
:optimizations :simple
:install-deps true
:npm-deps {:left-pad "1.2.0"}
:pretty-print true}}]})
And then, from your namespace, you can require it like so:
(ns left-pad-demo.core
(:require left-pad))
Or so:
(ns left-pad-demo.core
(:require ["left-pad" :as lp]))
A full working namespace could look like:
(ns left-pad-demo.core
(:require left-pad))
(defn -main [s length]
(console.log (left-pad s length)))
(set! *main-cli-fn* -main)
References:
Requiring Node.js modules from ClojureScript namespaces
ClojureScript is not an Island: Integrating Node Modules
I've been trying to create a user-defined exception in Clojure, and have been having all sorts of problems. I tried the method outlined here:
http://en.wikibooks.org/wiki/Clojure_Programming/Concepts#User-Defined_Exceptions
(gen-and-load-class 'user.MyException :extends Exception)
But that doesn't seem to work in Clojure 1.2 (or I'm doing something wrong...). My environment is Clojure 1.2, Emacs, and lein swank.
Thanks for your help!
Rather than generating custom classes, there are two much simpler ways to use custom exceptions:
Use slingshot - this provides custom throw+ and catch+ macros that let you throw and catch any object, as well as exceptions.
In clojure 1.4 and above, you can use clojure.core/ex-info and clojure.core/ex-data to generate and catch a clojure.lang.ExceptionInfo class, which wraps a message and a map of data.
Using this is straightforward:
(throw (ex-info "My hovercraft is full of eels"
{:type :python-exception, :cause :eels}))
(try (...)
(catch clojure.lang.ExceptionInfo e
(if (= :eels (-> e ex-data :cause))
(println "beware the shrieking eels!")
(println "???"))))
Or in a midje test:
(fact "should throw some eels"
(...)
=> (throws clojure.lang.ExceptionInfo
#(= :eels (-> % ex-data :cause))))
Make a file src/user/MyException.clj (where src is on CLASSPATH) containing:
(ns user.MyException
(:gen-class :extends java.lang.Exception))
Check the value of *compile-path* at the REPL. Make sure this directory exists and is on CLASSPATH. Create the directory if it doesn't exist; Clojure won't do so for you.
user> *compile-path*
"/home/user/foo/target/classes/"
user> (System/getProperty "java.class.path")
".......:/home/user/foo/target/classes/:......."
Compile your class:
user> (compile 'user.MyException)
user.MyException
If it worked, in *compile-path* you should now have files something like this:
/home/user/foo/target/
/home/user/foo/target/classes
/home/user/foo/target/classes/user
/home/user/foo/target/classes/user/MyException.class
/home/user/foo/target/classes/user/MyException__init.class
/home/user/foo/target/classes/user/MyException$loading__4410__auto__.class
Restart your Clojure REPL / JVM to load these classes. Again, make sure these new class files are on CLASSPATH. Now you should be able to use your class:
user> (user.MyException.)
#<MyException user.MyException>
FWIW, unless you are creating a custom exception for interop reasons you may want to consider using clojure.contrib.condition instead. It comes with a precompiled custom exception that you piggy-back custom data onto using it's API. I've been able to avoid creating many custom exceptions by using it instead. The docs are here:
http://clojure.github.com/clojure-contrib/condition-api.html