In clojurescript, how to evaluate a list - reactjs

Suppose having
(def defining-list `(def one 1))
How can I evaluate defining-list so that one becomes 1 ?
(in clojurescript)
EDIT:
I will give an idea of the broader image and what I am trying to accomplish here to avoid falling into an X/y problem.
I am trying to use cljsjs/material-ui from cljsjs package
Instead of defining each time a react component to use it as following:
(def app-bar
(r/adapt-react-class (aget js/MaterialUI (name :AppBar)))
I would like to define all the components from an array of tags:
(def material-ui-tags '[AppBar Avatar Backdrop])
So I was thinking if it's possible to do this without the usage of a macro as I found this
Something like:
(doseq [component material-ui-tags]
`(def ~(symbol (->kebab-case component)) (r/adapt-react-class (aget js/MaterialUI ~(name component)))))
But the above does only create a list of defs, I would like to evaluate these. In clojure eval would do the trick.

With reagent, you can use :> as shorthand for adapt-react-class as documented in https://github.com/reagent-project/reagent/blob/master/docs/InteropWithReact.md
Also, you can use dot notation with js/ and I think in shadow-cljs or cljs above 1.9.854 you can require to import the symbol instead of using aget.
In your case, it would be something like:
(ns example.core
(:require [MaterialUI]))
(defn component-two []
[:> MaterialUI/AppBar {:some-prop "some-value"}
[:div "children-here"]])
(defn component-two []
;; If the require above doesn't work
[:> js/MaterialUI.AppBar {:some-prop "some-value"}
[:div "children-here"]])
To do what you wanted using def, you either need eval or macro. Eval is not ideal as Jared Smith explained in the comment.
The example that you linked from reagent-material-ui uses macro. Invoking a macro actually performs expansion and then evaluation. So your code needs to be something like this:
clj file
(def material-ui-tags '[AppBar Avatar Backdrop])
(defmacro adapt-components []
(for [component material-ui-tags]
`(def ~(symbol (->kebab-case component)) (reagent.core/adapt-react-class (aget js/MaterialUI ~(name component))))))
cljs file
(adapt-components) ;; your defs will be available below this line
(defn my-component []
[app-bar ...])

Related

how to rerender reagent ui with react 18 and shadow-cljs reload?

how to rerender the whole reagent tree when we save file and shadow-cljs reloads?
react 18 has new createRoot api
and even before - if nothing changed calling render has no effect
With react v18, you need to create the root node only once. After this, you can call the .render() function from it to (re-)render your application.
Also, you need to configure a function / behavior to tell shadow-cljs what it should do on during reload.
Here is a full example, taken from here https://github.com/schnaq/cljs-reagent-template
(ns playground
(:require ["react-dom/client" :refer [createRoot]]
[goog.dom :as gdom]
[reagent.core :as r]))
(defn- main []
[:main.container.mx-auto
[:h1 "Welcome to your app"]])
;; -----------------------------------------------------------------------------
(defonce root (createRoot (gdom/getElement "app")))
(defn init
[]
(.render root (r/as-element [main])))
(defn ^:dev/after-load re-render
[]
;; The `:dev/after-load` metadata causes this function to be called
;; after shadow-cljs hot-reloads code.
;; This function is called implicitly by its annotation.
(init))
shadow-cljs is configured to call the init-function from playground/init.
;; shadow-cljs.edn
{...
:builds {:frontend {:modules {:main {:init-fn playground/init}}}}}
https://github.com/move-me-to-ipfs-shipyard/Fennec/blob/bbfb566211041dd57b419ffd95f642026bb989a8/src/Fennec/ui.cljs#L263
(:require
["react-dom/client" :as Pacha.dom.client]
[reagent.core :as Kuzco.core])
; seed.cljs
(defonce root {:dom-rootA (atom (Pacha.dom.client/createRoot
(.getElementById js/document "ui")))})
; ui.cljs
(:require [Fennec.seed :refer [root]])
(defn reload
[]
(when-let [dom-root #(:dom-rootA root)]
(.unmount dom-root)
(let [new-dom-root (Pacha.dom.client/createRoot
(.getElementById js/document "ui"))]
(reset! (:Pacha-dom-rootA root) new-dom-root)
(.render #(:Pacha-dom-rootA root)
(Kuzco.core/as-element [rc-current-page])))))

ReactTestUtils.Simulate.mouseDown

I'm trying to use the React Simulate function to simulate a mouseDown event for testing.
(defn mouse-down [node]
((.-mouseDown(.-Simulate ReactTestUtils) node (clj->js {:button 0})))
js translation:
ReactTestUtils.Simulate.mouseDown(node, {button: 0})
Nothing I've tried has resulted in an invocation of the mousedown listener--but when the listener is there when i try it in the browser where it works. It's just in the simulation.
What am I missing?
There are a couple mistakes in the Syntax here and your parens don't match. Generally it becomes easier if you reorganize the code a bit and -> can help there. As mentioned in the comment you want to use .mouseDown instead of .-mouseDown. I opted to use #js instead of clj->js since that is more optimal for static js objects such as this.
(defn mouse-down [node]
(-> (.-Simulate ReactTestUtils)
(.mouseDown node #js {:button 0})))
You can also make this a little more readable depending on where ReactTestUtils is coming from. I'm assuming its from the react-dom package which you just required.
;; in your ns
(:require ["react-dom/test-utils" :as ReactTestUtils])
;; which would allow
(defn mouse-down [node]
(ReactTestUtils/Simulate.mouseDown node #js {:button 0}))
;; or if you are on the latest CLJS version
(:require ["react-dom/test-utils$Simulate" :as sim])
;; and then
(defn mouse-down [node]
(sim/mouseDown node #js {:button 0}))

Problem with Autocomplete (Material UI + React + Reagent/ClojureScript)

I have a problem using the Material UI's Autocomplete with Reagent (ClojureScript). The element renders fine, but when I try to click on it, I get the following exceptions:
Uncaught TypeError: Cannot read property 'focus' of null
at handleClick (useAutocomplete.js:938)
at HTMLUnknownElement.callCallback (react-dom.development.js:189)
at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
at invokeGuardedCallback (react-dom.development.js:293)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:307)
at executeDispatch (react-dom.development.js:390)
at executeDispatchesAndReleaseTopLevel (react-dom.development.js:412)
at forEachAccumulated (react-dom.development.js:3260)
at runEventsInBatch (react-dom.development.js:3305)
at handleTopLevel (react-dom.development.js:3515)
useAutocomplete.js:322 Uncaught TypeError: Cannot read property 'removeAttribute' of null
at eval (useAutocomplete.js:322)
at eval (useEventCallback.js:26)
at eval (useAutocomplete.js:433)
at eval (useEventCallback.js:26)
at eval (useAutocomplete.js:463)
at eval (useAutocomplete.js:528)
at commitHookEffectListMount (react-dom.development.js:19765)
at commitPassiveHookEffects (react-dom.development.js:19803)
at HTMLUnknownElement.callCallback (react-dom.development.js:189)
at Object.invokeGuardedCallbackImpl (react-dom.development.js:238)
Breaking in JS debugger, I see that inputRef.current is null (which is what focus and removeAttribute are called on. (Oddly enough, the only place where inputRef is set in the file is by calling useRef(null), which is what results in inputRef.current being null.)
In my code, I define the Autocomplete field as follows:
(ns my-product-redacted.views.atoms
(:require [reagent.core :as r]
["#material-ui/lab/Autocomplete" :default Autocomplete]
;; other requires
))
(def autocomplete-field (r/adapt-react-class Autocomplete))
Then, in a React component, it is used as follows:
[a/autocomplete-field {:render-input (fn [js-params]
(let [clj-params (js->clj js-params)
params {:label label
:width width
:select select?
:Input-label-props {:shrink true}
:Select-props {:native true}}
all-params (into clj-params params)]
(js/console.log (clj->js all-params))
(r/as-element [a/text-field all-params])))
:options (when select? (cons {:value "" :label ""} options))
:get-option-label (fn [option] (or (get (js->clj option) "label") ""))
:default-value (when (not select?) value-override)
:value (when select? value)
:disabled disabled?
:on-focus #(re-frame/dispatch [::forms/on-focus path])
:on-blur #(re-frame/dispatch [::forms/on-blur path])
:on-change #(re-frame/dispatch (conj on-change (-> % .-target .-value)))})]
(Here, a/text-field is also defined in the same namespace as a/autocomplete-field and in a similar way.)
The JS console log (from the (js/console.log (clj->js params)) call) shows that inputProps.ref.current is set to null. However, InputProps.ref is not null. Even so, I tried to manually associate the same function as is passed with InputProps.ref to inputProps.ref.current, but that made no difference.
I have also tried the workaround suggested in https://github.com/mui-org/material-ui/issues/21245 (although that issue is with the Gestalt library, not with Reagent, it suggests that there may be a problem with ref forwarding). But wrapping the text-field into a div with the ref taken from InputProps.ref also made no difference.
Any suggestions?
Calling js->clj on the render-input js params breaks them.
I put a quick demo of the material UI autocomplete component in my demo repository here.
But it's mainly taken from the official reagent docs/examples here:
(defn autocomplete-example []
[:> mui/Grid
{:item true}
[:> Autocomplete {:options ["foo" "bar" "foobar"]
:style {:width 300}
;; Note that the function parameter is a JS Object!
;; Autocomplete expects the renderInput value to be function
;; returning React elements, not a component!
;; So reactify-component won't work here.
:render-input (fn [^js params]
;; Don't call js->clj because that would recursively
;; convert all JS objects (e.g. React ref objects)
;; to Cljs maps, which breaks them, even when converted back to JS.
;; Best thing is to use r/create-element and
;; pass the JS params to it.
;; If necessary, use JS interop to modify params.
(set! (.-variant params) "outlined")
(set! (.-label params) "Autocomplete")
(r/create-element mui/TextField params))}]])

CommonLisp Function for dynamically looking up bindings in packages

Is there a way to dynamically ask for bindings in another package, and by dynamically i mean by not knowing the exact name of a binding in some package. A concrete case would be:
As in package B, i know there exists a package A which has a certain class and i extract all direct slots of that class by (in LispWorks):
(setq direct-slots (mapcar #'slot-definition-name
(class-direct-slots (class-of class-in-package-A))))
Now i want to bind those slots to some values using MAPCAR:
(mapcar #'(lambda (slot) (list slot
(funcall slot class-in-package-A)))
direct-slots)
This doesn't work since i am in package B and need package precision for the call to (funcall slot class-in-package-A), packageA::slot is obviously wrong. Is there a function for this which searchs for a certain symbol in a package?
If you have a slot-name and want to get the value of the named slot in some object, use slot-value:
(mapcar (lambda (slot-name)
(slot-value some-object slot-name))
slot-names)
Slot names are symbols, and they will not magically lose their package if you happen to “be” in a different package. I think your confusion is that you are thinking about accessors, but those are a different thing (they use something like slot-value internally).
CL-USER> (defpackage #:foo
(:use #:cl))
#<PACKAGE "FOO">
CL-USER> (defpackage #:bar
(:use #:cl #:sb-mop)) ; in SBCL
#<PACKAGE "BAR">
CL-USER> (in-package #:foo)
#<PACKAGE "FOO">
FOO> (defclass afoo ()
((a :initarg :a)
(b :initarg :b)))
#<STANDARD-CLASS FOO::AFOO>
FOO> (in-package #:bar)
#<PACKAGE "BAR">
BAR> (mapcar #'slot-definition-name
(class-direct-slots (find-class 'foo::afoo)))
(FOO::A FOO::B)
BAR> (let ((slot-names (mapcar #'slot-definition-name
(class-direct-slots (find-class 'foo::afoo))))
(obj (make-instance 'foo::afoo
:a 1
:b 2)))
(mapcar (lambda (slot-name)
(slot-value obj slot-name))
slot-names))
(1 2)
In general, you should be using accessors in “user” code, and you should know which accessors exist for a given object. It also shouldn't matter for user code whether something is a direct slot.

Simple example of Sandbar or Ring sessions in Google App Engine

I'm trying to work out how to get sessions and flash working in Google App Engine. Could someone provide a clear example using either Ring or Sandbar? I think I have sandbar working, specifically it doesn't tell me that Var sandbar.stateful-session/sandbar-flash is unbound and when I dump the handler I get :flash and :session though I'm not certain if that is a sandbar session or a ring one. For completeness I will mention that I am using the latest versions of appengine-magic, ring, hiccup and sandbar. There do not appear to be any incompatibilities or issues.
So a clear example preferably with use of flash-put!, flash-get, session-put! and session-get.
I don't usually like answering my own questions, however in this case I'll make an exception because:
a) There isn't a lot of easy to understand examples out there.
b) It would be nice to have a quick working example for others to use.
Note: appengine-magic is not required here, this will also work with normal ring sessions
Code
;; Place in a file called session.clj in the example project
(ns example.session
"Works with the normal ring sessions
allowing you to use side-effects to manage them")
(declare current-session)
(defn wrap-session! [handler]
(fn [request]
(binding [current-session (atom (or (:session request) {}))]
(let [response (handler request)]
(assoc response :session #current-session)))))
(defn session-get
([k] (session-get k nil))
([k default] (if (vector? k)
(get-in #current-session k)
(get #current-session k default))))
(defn session-put!
([m]
(swap! current-session (fn [a b] (merge a m)) m))
([k v]
(swap! current-session (fn [a b] (merge a {k b})) v)))
(defn session-pop! [k]
(let [res (get #current-session k)]
(swap! current-session (fn [a b] (dissoc a b)) k)
res))
(defn session-delete-key! [k]
(swap! current-session (fn [a b] (dissoc a b)) k))
(defn session-destroy! []
(swap! current-session (constantly nil)))
;; Place in a file called core.clj in the example project
(ns example.core
(:use compojure.core
[ring.middleware.keyword-params :only [wrap-keyword-params]]
[ring.middleware.session :only [wrap-session]]
[ring.middleware.session.cookie :only [cookie-store]]
[example session])
(:require [appengine-magic.core :as ae]))
(declare current-session)
(defroutes example-app-routes
(GET "/" _
(fn [req]
(let [counter (session-get :counter 0)]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (str "started: " counter)})))
(GET "/inc" _
(fn [req]
(let [counter (do
(session-put! :counter (inc (session-get :counter 0)))
(session-get :counter))]
{:status 200
:headers {"Content-Type" "text/plain"}
:body (str "incremented: " counter)}))))
(def example-app-handler
(-> #'example-app-routes
wrap-keyword-params
wrap-session!
(wrap-session {:cookie-name "example-app-session"
:store (cookie-store)})))
(ae/def-appengine-app example-app #'example-app-handler)
How to use it
Navigating to http://127.0.0.1:8080/inc increments the counter in the session and http://127.0.0.1:8080/ will display the value of counter in the session.
wrap-session! is not required for sessions to work, just using
(wrap-session {:cookie-name "example-app-session"
:store (cookie-store)})
will give you working functional sessions. However I wanted to manage my sessions with side-effects and wrap-session! provides that functionality. To use flash like functionality, simply use session-put! to put a value into the session and then use session-pop! to remove it.
Hope that's helpful.
If you want to use the Sessions provided by GAE, you can use the following
https://gist.github.com/1095841
to include a ring like session in your requests, but based on GAE session support.
If you want stateful sessions on top of this, you can use the stateful session API provided by Sandbar
https://github.com/brentonashworth/sandbar

Resources