Fork me on GitHub
Sameer Thajudin04:05:09

What is the fastest and easiest way to build front end UI components using Clojurescript?


Shadow-cluster and reagent



👍 2

maybe I'm beating a dead horse here and this is a silly question.. but would it make sense with an option to the cljs compiler that disabled minification for all JS interop? Most of my cljs are calls to my own code, it's only a handful of places where I call (.someMethod js-thing) etc. with this "externs safe mode" you'd get (slightly?) larger output files, but the benefit of never having to deal with externs


lots of internal CLJS code does a lot of JS interop, basically everything is just JS interop in the end. so the trouble comes from not reliably figuring out what can and can't be renamed. definining "your calls" is the hard part here. if you use a macro that ends up generating interop calls, is that your code or not? 😛


but externs inference takes care of all of this pretty much?


ah, good point


@emaun you'll have to look at the git logs - probably some kind of fix for some consumers

🙌 1

I have a Clojurescript app that uses Reagent with Preact instead of React. Everything works fine except Fragments, which for some reason I cannot access with react/Fragment. Interestingly, I can (:import (react Fragment)) in the ns declaration and then use Fragment in a (react/createElement Fragment etc) call, or I can access Fragment with object property syntax (.-Fragment react). I just can't access Fragment using the normal syntax for referencing a var in a namespace, react/Fragment. Anyone have a guess as to why this is happening?


I just tried this:

(ns ...
  (:require ["react" :as react]
            ["preact" :as preact]))

(js/console.log "React's Fragment" react/Fragment)
(js/console.log "Preact's Fragment" preact/Fragment)
And it seems to be working fine, at least with shadow-cljs and the latest version of Preact. What do you mean by "cannot access" exactly?


Hi @p-himik, thanks for the reply. By "cannot access", I mean react/Fragment is undefined. I'm using a modified build of Preact, so clearly I've done something to create this situation. I'm curious what could cause a javascript "namespace" like preact to not work with the ns/function syntax but work with (.-function ns) .


Dunno. But exactly was Preact modified?


And do you require it in the same way as I do it above?


I'm including preact and preact-compat as foreign libs in the clojurescript compiler config, and preact-compat :provides ["react", "react-dom", "create-react-class"] . The change to preact and preact-compat was to remove all the different module styles at the top (the original built script has support for ES modules, commonjs, and defining a global variable), and only leave in the global variable definition. I require preact in my code as react because of the :provide config I'm using; (:require [react]) Also note I'm not using shadow-cljs, I'm calling the clojurescript compiler directly via clojure code.


> and only leave in the global variable definition But why? There being multiple modules shouldn't affect anything - in the end, only one of them is compiled in.


If you do what you've done without changing Preact itself, does react/Fragment work?


The multiple module definitions were present in the script after advanced compilation. I changed that code in an effort to shrink the script a bit more. I'll have to check whether react/Fragment works without those changes.


> The multiple module definitions were present in the script after advanced compilation That's... bizarre. I've never even heard about that, so maybe there's something else wrong in your setup. I'd try fixing that instead of bandaging it by changing your dependencies.


Just tried (with shadow-cljs, since I don't have a raw CLJS compilation set up) compiling an optimized build - there's only one reference to preact in there, to one file.


Ok, thanks for the help. I'll see if I can simplify the way I'm using the dependencies to avoid this issue.

👍 1
Michelle Lim16:05:14

Reagent folks, did you know you it's possible to use React hooks in your components by making them functional (instead of class components)? I thought I was late to the party, but am finding out that others also had no idea, so I wrote up a quick summary. Hope this helps someone!

🙌 1

You could use hooks even before :f>. :) Also, I'd recommend making that (fn [] ...) in your article a proper defn. It'll get a proper name, it will not be re-instantiated on each render of the parent component, it will work nicely with Reagent cache that way.


Thanks @U01B301AJV8 ! it is still not anywhere mentioned in the docs unless you go digging for it, so thanks for calling it out @p-himik if you could use hooks before :f> then what value does it provide?


also wondering why reagent doesn't provide a use-ratom hook so then :f> isn't needed


It is mentioned in the docs: > if you could use hooks before :f> then what value does it provide? Easier usage of existing functional components. > why reagent doesn't provide a use-ratom hook so then :f> isn't needed Why wouldn't it be needed? Functional components can be used even when no hooks are used at all. Apart from that, there are many existing hooks - nothing new that Reagent can introduce would alleviate the need for functional components here.


> "mentioned in the docs unless you go digging for it" but yes it is there. (but not here You don't need :f> as :> works - so you'd use use-ratom inside a function and render it with :> that was my point


> (but not here Because it's not a documentation website, it's a blog. :) But yeah, the docs could be improved and made more discoverable: > so you'd use use-ratom inside a function What about every single hook that already exists out there? How would you use those?


I don't follow. Why wouldn't this work from Michelle's example:

(defn example []
  [:> ;; <-- make pass through untouched
   (fn []
     (let [[count set-count] (react/useState 0)] ; <---
       (react/useEffect ; <---
         #(set! (.-title js/document) (str "You clicked " count " times"))
         #js [checked])
        [:p "You clicked " count " times"]
        [:button {:on-click #(set-count (inc count))}
         "Click me"]]))])


it says you can't use RAtoms there - so that's my point -just make a hook


to make it concrete - what i'm thinking is making this part of the API as use-ratom or similar


> Why wouldn't this work from Michelle's example: Because it doesn't have a call to reagent.core/as-element. The docs do mention it. > it says you can't use RAtoms there - so that's my point -just make a hook Not every reactive thing is a ratom. Cursors won't work there, as well as reactions, and tracks. Instead of adding many things for every use case, wouldn't it be both simpler and easier to just add one :f>, exactly as it is done now? There might easily be other differences that I'm not aware of - specifically because :f> is not just a wrapper around :>. It has its own implementation, with its own logic.


yes, use-reagent - my point is a hook that performs that integrates with reagent. Agreed you'd probably end up with :f> at the end. Thanks for the discussion

👍 1

I don’t have a firm grasp on hooks, still learning react and reagent. But what is the #js [checked] portion as the final argument in the call to react/useEffect? I can’t see where checked is coming from, to my untrained eye it looks like a symbol that came out of nowhere.

Michelle Lim18:05:55

@UTFAPNRPT that's a typo from a previous version of the example! it's supposed to be count. thank you so much for catching it, fixed now


Great article Michelle! I actually didn’t know that and I’ve been doing reagent/reframe for yearssss. I think you’re right that when things “arrive quietly” its easy to miss. Thanks for pointing out 🎉

💯 1

I need some help in providing an async function to a security framework (Okta) ... how would one provide a CLJS equivalent of this?


const restoreOriginalUri = async (oktaAuth, originalUri) => {
  history.replace(toRelativeUrl(originalUri || '/', window.location.origin));


the literal equivalent of this would be

(defn restore-original-uri [okta-auth original-uri]
   (fn [res _rej]
     (res (.replace js/history (toRelativeUrl (or original-uri "/") (.. js/window -location -origin)))))))


oh - nice!


that example doesn't make sense to me, since there is no await?


(but also I don't write modern JS with any regularity, so maybe I don't know this pattern)


maybe I just need something like p/create


that is not what the MDN article saying


await is mandatory to await a promise


whether you need to even do that is up to you


I'm reading it differently - it says zero or more await expressions


> Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.


you are not reading it differently, but maybe you don't understand what it means


async does not magically await promises


anyways, there is an oddity which I can't find an answer to which that maybe async will resolve promise arguments that news to me


that's what the StackOverflow question seems to imply


ah no that is not what is happening


I see, the SO question is just about not checking for null that is all, async does not magically resolve promise arguments


this is failing when the return promise fails


anyways the SO question seems obfuscated - I don't think you need async anyway


it's just a callback


@raymcdermott To summarize the above, just write it as if there was no async. Unless you need to use some async functionality inside the function - then just return a promise.


it might be that the API needs you to return a promise, and they are using async here as syntactical shortcut


but couldn't find that out, and I don't have the patience to look at all this Okta docs


haha I feel you 🙂


thanks for the advice @dnolen and @p-himik


They're just using plain await which works on any value, not just promises. And in the TS type definitions they explicitly say that they expect a function that returns a promise (which isn't actually needed). @raymcdermott It's probably not applicable in your case, but beware of returning JS objects that have a field then in them that points to a function. Due to the mess that JS is, such objects will be treated as implicit promises.


I recon there must be a good reason why the g flag is not supported for ClojureScript regexes. But this gets a bit confusing:

(js/RegExp. "foo" "g")
=> #"foo"

(.replace "foo foo" (js/RegExp. "foo" "g") "bar")
=> "bar bar"
I am very happy this works (because re-seq is not an easily available option for me right now), but anyway.


Can I break out of a doseq?


Ah, :while. Cool!