Can anyone recommend a simple way to evalue a user generated expression within my CLJS app (e.g. eval but sandboxed). An interpreter is fine really - not super worried about performance. I see references to cljs.js
and also borkdude/sci
which sound like candidates. Not familiar with pros/cons of different approaches.
Use case is to allow user configured behaviour of single page webapp without recompiling frontend. (e.g. setting component properties based on simple formula)
cljs.js
is not sandboxed at all
if the language is very restricted I would probably just consider writing your own interpreter
@olivergeorge yes, that is one of the use cases of sci. Itโs also used by Chlorine/Clover for configuration
Thanks guys
How do I use react from cljs? I'd like to use react/useEffect
, but importing react with ["react" :as react] ;; shadow-cljs
gives me this error in the developer console:
env.cljs:198 error when calling lifecycle function mount-components Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
I'm guessing the reason is point 3 here since I am likely importing react
a second time.
The code example I am trying to get to work is taken from https://juxt.pro/blog/react-hooks-raw#_side_effects (but using js/React.useEffect
like in that example did not work).are you using useEffect
inside of a defn
?
Yes.
@endrebak85 how are you rendering the function component that calls useEffect
?
are you using a React wrapper like reagent?
I am rendering it in hiccup like this: [render-function-from-example]
.
I am using reagent in my code, yes ๐
you cannot use React hooks inside normal reagent components
Understood, thanks.
those get turned into classes. and the warning "Hooks can only be called inside of the body of a function component" speaks right at that
here's how to use React hooks in reagent: https://github.com/reagent-project/reagent/blob/master/doc/ReactFeatures.md#hooks
if you're using reagent 1.0 or above, you can render your component like:
[:f> render-function-from-example]
Thanks, now it works :)
I was advised to use useEffect
to be able to update DOM elements with D3 graphs after the DOM elements had rendered. I think I am close, but that I am making a mistake here:
(defn graph-page []
(let [[graph set-graph]
(react/useState ;; the state is a dagre graph
(-> (dagre/graphlib.Graph.)
(.setGraph (clj->js {}))
(.setDefaultEdgeLabel (fn [] {}))
(.setNode "hi" (clj->js {:label "hi" :width 144 :height 100}))
(.setNode "ciao" (clj->js {:label "ciao" :width 144 :height 100}))
(.setEdge "hi" "ciao")))
render-graph (fn [graph] ;; this renders the graph with dagre-d3
(js/console.log "render-graph is executed!")
(let [renderer (dagre-d3/render)
svg (d3/select "svg")
inner (-> svg
(.append "g"))
_ (renderer inner graph)]))]
[:div
[:svg]
[:button {:on-click #(set-graph (render-graph %))} "Graph!"]]))
Is this correct? Clicking the button just adds an empty <g></g>
to the DOM (from render-graph
). I am wondering whether the error is in my use of hooks, or my use of the dagre-d3 library (proper JS-usage of the code I am trying to write use in Clojurescript is https://dagrejs.github.io/project/dagre-d3/latest/demo/sentence-tokenization.html).
One potential problem with my code is that graph
is not something that should be included in the hiccup, but it the graph that should be rendered.A most trivial item of trivia, but: I was rather surprised to find that defprotocol
evaluates to false
. I had a quick look at the GitHub docs and the last line of the defprotocol
macro is a (set! ~'*unchecked-if* false)
, so I guess thatโs it. What should defprotocol
evaluate to?
With [:button {:on-click #(set-graph (render-graph graph))} "Graph!"]
it renders.
I didn't see it described anywhere, but FWIW Clojure explicitly returns the symbol for the protocol. So CLJ and CLJS behavior here is different. But if the behavior itself is not described anywhere, then it's probably fine.
I believe most programs completely ignore the value returned from such expressions.
(render-graph %)
would pass the button event to the render-graph
fn which doesn't seem right
your change seems correct
But is not my code equivalent to the one from the reagent example?
(defn example []
(let [[count set-count] (react/useState 0)]
[:div
[:p "You clicked " count " times"]
[:button
{:on-click #(set-count inc)}
"Click"]]))
[:button {:on-click #(set-graph (render-graph %))} "Graph!"]
expand this to:
[:button {:on-click (fn [button-event]
(render-graph button-event))} "Graph!"]
why would you pass the DOM button-event
to render-graph
?
it expects a graph!
#(set-count inc)
passes inc
to set-count
, not the button-event too
Ah, I see. Thanks for your patience.
@cassiel that expression is really a hint for the compiler
True enough - but seeing false
when I mash a defprotocol
in Emacs is disconcerting.
You are welcome to add a question on http://ask.clojure.org about this if it disconcerts you enough. Realize that it may not disconcert the ClojureScript maintainers as it does you. I do not know.
Iโll think about how disconcerted I am.
hi, @dnolen https://www.reddit.com/r/Clojure/comments/mcce4h/everything_i_have_learned_i_have_learned_from/ "29:36 - is source code for this available anywhere ?"
would ask @dnolen
@rafal.wysocki in core.match
funny story I think someone from Apple was there and at least some of that paper ended up in the Swift pattern matcher?
Also funny story: I shared the core.match literature with someone which almost turned into a thing to optimize state machines in Yosys, but I don't think it was ever completed.
Is there a way to tell if we're in the Clojure cljs macroexpansion stage vs just regular Clojure?
Here's my motivation:
I have a log util namespace util.clj
with macros like
(defmacro logp
"Impl for log macros. Log `args` at `level`."
[level & args]
(macros/case
:clj
`(clojure.tools.logging/logp ~level ~@args)
:cljs
`(js-logp ~level ~@args)))
(I'm using https://github.com/cgrand/macrovich here).
When expanding a macro for CLJS compliation, I want js-logp
. When expanding a macro for Clojure (in normal usage), I want clojure.tools.logging
.
My issue is one of may namespaces that gets loaded during the Clojure cljs macroexpansion stage does some logging as a side-effect of being loaded. So during cljs macroexpansion it's expanding the macro as clojure.tools.logging.logp
in Clojure land in order to do the macroexpansion. However, I don't have clojure.tools.logging
as a dependency in that situation, so the macroexpansion is causing my Clojurescript build to fail. (This code is also used elsewhere as a sub-project where clojure.tools.logging
is available.)
I want to do something like
(defmacro logp
"Impl for log macros. Log `args` at `level`."
[level & args]
(when-not *clojurescript-macroexpansion*
(macros/case
:clj
`(clojure.tools.logging/logp ~level ~@args)
:cljs
`(js-logp ~level ~@args))))
and have the macro expand to nil
or some other sort of no-op if we're expanding it while running in Clojure to do ClojureScript macro expansion. Is there some way I can possibly figure out if the code is running in "Clojure for ClojureScript macroexpansion mode"?Found this recently while digging, I think it's what you're after https://github.com/plumatic/schema/blob/master/src/clj/schema/macros.clj#L10
I could be wrong but I think this is what deftime
(from Macrovich) is for