Fork me on GitHub
#clojurescript
<
2021-05-14
>
Oliver George02:05:48

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.

Oliver George02:05:48

Use case is to allow user configured behaviour of single page webapp without recompiling frontend. (e.g. setting component properties based on simple formula)

dnolen02:05:57

cljs.js is not sandboxed at all

dnolen03:05:11

if the language is very restricted I would probably just consider writing your own interpreter

borkdude06:05:05

@olivergeorge yes, that is one of the use cases of sci. It’s also used by Chlorine/Clover for configuration

Endre Bakken Stovner15:05:08

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).

dpsutton15:05:19

are you using useEffect inside of a defn?

lilactown15:05:35

@endrebak85 how are you rendering the function component that calls useEffect?

lilactown15:05:43

are you using a React wrapper like reagent?

Endre Bakken Stovner15:05:34

I am rendering it in hiccup like this: [render-function-from-example].

Endre Bakken Stovner15:05:50

I am using reagent in my code, yes 🙂

lilactown15:05:05

you cannot use React hooks inside normal reagent components

Endre Bakken Stovner15:05:16

Understood, thanks.

dpsutton15:05:26

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

3
🙏 3
lilactown15:05:57

if you're using reagent 1.0 or above, you can render your component like:

[:f> render-function-from-example]

Endre Bakken Stovner15:05:29

Thanks, now it works :)

Endre Bakken Stovner17:05:41

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.

Endre Bakken Stovner17:05:31

With [:button {:on-click #(set-graph (render-graph graph))} "Graph!"] it renders.

lilactown17:05:58

(render-graph %) would pass the button event to the render-graph fn which doesn't seem right

lilactown17:05:26

your change seems correct

Endre Bakken Stovner17:05:54

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"]])) 

lilactown17:05:00

[:button {:on-click #(set-graph (render-graph %))} "Graph!"]
expand this to:
[:button {:on-click (fn [button-event]
                      (render-graph button-event))} "Graph!"]

lilactown17:05:14

why would you pass the DOM button-event to render-graph?

lilactown17:05:20

it expects a graph!

lilactown17:05:07

#(set-count inc) passes inc to set-count, not the button-event too

Endre Bakken Stovner17:05:23

Ah, I see. Thanks for your patience.

cassiel17:05:59

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?

p-himik17:05:01

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.

andy.fingerhut17:05:53

I believe most programs completely ignore the value returned from such expressions.

cassiel17:05:12

True enough - but seeing false when I mash a defprotocol in Emacs is disconcerting.

andy.fingerhut17:05:04

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.

cassiel17:05:59

I’ll think about how disconcerted I am.

dnolen17:05:18

@cassiel that expression is really a hint for the compiler

dnolen18:05:41

@rafal.wysocki in core.match

❤️ 3
dnolen18:05:26

funny story I think someone from Apple was there and at least some of that paper ended up in the Swift pattern matcher?

💥 2
Pepijn de Vos18:05:42

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.

Cam Saul22:05:16

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"?

lsenjov23:05:01

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

👍 3
Jeff Evans14:05:12

I could be wrong but I think this is what deftime (from Macrovich) is for

👍 4