Fork me on GitHub
#clojurescript
<
2022-02-14
>
nivekuil02:02:07

How will clojurescript implement parse-long from the upcoming clojure version, in light of js/MAX￱￱SAFE￱￱INTEGER? Will it upgrade to the closure type instead of native number, or the new bigint?

Alex Miller (Clojure team)03:02:01

prob best to ask this in #cljs-dev - I do believe they have been working on this but not sure of answers to those questions

quoll05:02:43

No. The idea is to duplicate Clojure functionality as closely as possible, and Clojure does not do any upgrading. Also, those types already have their own parser so it's not about providing missing functionality, but rather, compatibility with Clojure source code

nivekuil10:02:43

thanks, I see that there's been work done on this already in https://clojure.atlassian.net/browse/CLJS-3348. as far as functionality or compatibility, it's not clear whether that means sticking close to the process of how Clojure does the parsing (passing through to native semantics as thinly as possible) or to the outcome. js/parseInt will parse "72775727266264526"to 72775727266264530, which is a behavior that just bit me having been "corroborated" by two different tools, but not in my own codebase which uses goog.math.Long to pass data more properly between front/backend

nivekuil10:02:40

I understand that cljs has typically preferred the native-semantics approach, but in practice I will always consider the use of a parse-long that just passes through to parseInt to be an mistake; the behavior is just too insidious to be considered truly compatible

nivekuil10:02:12

the situation seems somewhat akin to format not being available in cljs, fwiw

quoll12:02:24

Let me ask David about moving to a closure long. I have been very unsatisfied with the MAX_SAFE_INTEGER limit. The problem with things like BigInt is native operators suddenly not working. I forget what goes on with these Google longs

kamilwaheed07:02:15

New to CLJ(S), I’ve been experimenting with idea of replacing hot code reloading with REPL in UI development (React) in my workflow. What I wanted was to evaluate a component definition and see it immediately re-render in the browser. My hypothesis was that this would not only be significantly faster, but would be guaranteed to not become bloat-y as the app size grows (esp. from my experience with hot code reloading in JS that starts feeling bloat-y after a while) I tried first with the experimental React Fast Refresh support in helix on a test project. I added a post eval hook to cider (`cider-after-eval-done-hook`) that would automatically evaluate (helix.experimental.refresh/refresh!) which re-renders any components that have been changed in the browser. It felt extremely snappy. Today, I wanted to bring this workflow to my day job with an actual mid sized app. We use hx which doesn’t have React Fast Refresh support. The simplest way was to re-render the root as a cider eval hook. I felt like that this would still be a lot faster than hot code reloading. We’re using shadow cljs and it easily takes ~2 seconds (>2 seconds sometimes) after saving a file. Turns out, it is. And my development workflow feels a lot snappier and I am getting instant (60+ fps) feedback (unless rendering root is slow; but can be fixed with React Fast Refresh which only re-renders “dirty” components). My understanding when I started CLJS professionally a few months ago was that the status quo in UI development (react) is still hot code reloading and not a REPL-driven workflow. I can’t help but wonder who else has this kind of workflow in UI development and why this isn’t more popular?

p-himik11:02:21

As just my 5 c. I've read and watched every recommended article and video on REPL-driven workflow. I have tried it for a while - in fact, that's how I started Clojure development initially. And I don't like it at all. For two reasons: • It makes it obligatory to have to remember what exactly I haven't reloaded yet • It makes obscene constructs like #' absolutely necessary, and for them to be put there in advance, in places "that you might potentially reload" I very much prefer the reloaded workflow where the only thing I have to remember is to press that keyboard shortcut to reload what's needed. The fact that it can take seconds as opposed to milliseconds is absolutely of no importance to me given that I tend to write large chunks of code while modeling what will happen in my head, and reloading all that only once the full picture is there.

kamilwaheed11:02:36

Yep, that totally makes sense. I have had some success working on some small feature additions in Clojure (without hot code reloading) where I’m “patching” the running app by evaluating blocks of code. Where it started proving valuable was when I started to interact with the state of the running app. I have noticed myself doing that more and more with the running app in the browser as well i.e. investigating why an unexpected thing is being rendering by looking at the state atom and then just swap!(ing) it immediately to verify This has happened rarely but when it has happened, it has been extremely rewarding. Thanks for your 2 cents!

p-himik12:02:19

Ah, right - the idiom is "2 cents", not "5 cents". :D I did feel something was a bit off, by about 3 c.

😆 1
p-himik12:02:59

But I'd say that swap!ing the state is completely orthogonal to changing the code. And in Reagent apps, everything will react to a state change - conceptually, it's actually much more similar to the reloaded workflow than to a REPL-driven one.

p-himik12:02:20

An analog for a REPL-driven workflow in the state management area would be changing .-props directly on an instance of a component, I think.

👍 1
simongray06:02:02

I sort of agree with @U2FRKM4TW in that I don’t like REPL-driven dev in ClojureScript. However, in Clojure (JVM) I use it permanently (and love it) and would hate a “reloaded workflow”. I think the primary difference is that the things I do in Clojure are things that actually take some time to (re)build while a CLJS web app is comparatively light, as you can’t really make something like that very heavy when it’s supposed to go on the Internet.

p-himik07:02:55

Just in case - a reloaded workflow doesn't mean automatic code reloading.

kamilwaheed10:02:26

That’s interesting. Beyond just the aspect of ease/speed, I wonder how else can a REPL workflow help in terms of understanding the running app. I have found REPL incredibly helpful in certain cases when inspecting/updating the state atom. Dev tools in browsers also fall in the category of interactive development workflow to help you inspect/understand the app in its running state. There’s a little feature in Chrome where you can right-click on any DOM element (or printed value in Console) and assign that to a globally accessibly variable. Once assigned, you can play with it for quick experiments in the console (browser REPL). This is great, except that it’s disconnected from the source.

p-himik10:02:40

Some things worth noting: • You don't have to explicitly assign a DOM node to a variable - the currently selected node is always $0, the previous one is $1, and so on. Similar to how we have *1, *2, and *3 in Clojure REPL • I find tools like Reveal and Portal in combination with tap> to be much more useful for data exploration than REPL. Of course, some objects require additional code - and at least Portal can send you the data in the current view back to the REPL • If you use React, install React Developer Tools browser extension. It's helpful even if you use only CLJS and not JS

kamilwaheed10:02:17

Yep, good points! Thanks

kamilwaheed10:02:08

@U2FRKM4TW On your second point, to get a piece of data over to Portal / Reveal (neither of which I am really familiar with), you would still use REPL, right? The primary purpose those tools serve is making interacting with big chunks of data nicer compared to a REPL prompt or inline results or whatever editors/REPLs come with by default, correct?

p-himik11:02:01

You'd use the built-in function tap>. Usually people just put it in the relevant places in the code - it can even stay there in production as it will be a no-op if there are no taps attached. But you can of course also use it in REPL if you already have the data available there. Note that people generally recommend against typing directly into the REPL input and instead evaluate forms or whole files within a REPL session using your IDE REPL integration. > The primary purpose those tools serve is making interacting with big chunks of data nicer compared to a REPL prompt or inline results or whatever editors/REPLs come with by default, correct? That's one of the goals. I wouldn't say "nicer" though - just how a hammer is not a nicer tool than a boulder to hammer in nails. :) There are many other features as well.

👍 1
pez11:02:51

I am running into a lack of await 😃 . In JS I can do:

function one() {
  return new Promise((resolve, _reject) => {
    return resolve(1)
  })
}

function timesTwo(x) {
  return 2 * x;
}

async function foo() {
  return one().then((x) => timesTwo(x))
}

await foo() // => 2
But how to get the value, not the promise, out of foo() in ClojureScript?
(defn one+ []
  (js/Promise. (fn [resolve _reject]
                 (resolve 1))))

(defn time-two [x]
  (* 2 x))

(defn foo []
  (-> (one+)
      (.then (fn [x] (time-two x)))))

(foo) ; => #object [Promise [object Promise]]

p-himik11:02:25

No way - you have to use (.then (foo) (fn [result] ...)), ideally also with a .catch somewhere.

p-himik11:02:56

BTW, maybe it'll save you a few seconds of typing the next time. :) Instead of that large new Promise(...), you can just write Promise.resolve(1).

🙏 1
oxalorg (Mitesh)12:02:14

I typically use kitchen-async https://github.com/athos/kitchen-async whenever I can. It's async/await like similar syntax sugar for promises

👀 1
emccue20:02:07

@U0ETXRFEW promesa. hard reccomend

emccue20:02:17

it would become

emccue20:02:50

(defn foo []
  (p/let [val (one+)]
    (time-two val)))

p-himik20:02:11

Neither of those answer the initial question though. And that code above is not simpler than

(.then (one+) time-two)

emccue02:02:28

well his "top level await" thing doesn't work in js either afaik

pez06:02:33

It actually did in the browser console when I tried it.

p-himik08:02:06

Browser console is magic, some things can easily be different there. But also, JS does have top level await, in fact it's the same name, without quotes. :) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await

🙏 1
pez17:02:29

So, trying with channels. I created this Reagent component:

(defn- when-promise-resolved
  [comp]
  (fn [comp]
    (let [comp-chan (a/chan (a/sliding-buffer 1))]
      (-> (js/Promise. (fn [resolve] (js/setTimeout resolve 10000)))
          (.then (fn []
                   (js/console.log "promise resolved!")
                   (a/offer! comp-chan comp))))
      (a/poll! comp-chan))))
And thought I could use it to wait on a promise before rendering....
[when-promise-resolved [:div "BOOM!"]]
But after seeing the "promise resolved!" message in the console, I get no "BOOM!" div rendered.

p-himik17:02:30

a/poll! does not wait - it just returns nil in your case. Check its docstring. When a/offer! happens, there's nothing that tells Reagent to re-render the component since none of the properties or deref'ed ratoms have changed. Also, if you're deciding on whether to use core.async or promises, I would advise against combining them together. It's a recipe for a mess and impossible to debug disasters. I'd stick with promises if there are no complex async workflows in your code. And with core.async if there are. Try this instead:

(defn when-promise-resolved [comp]
  (r/with-let [render? (r/atom false)
               _ (.. (js/Promise. (fn [resolve _]
                                    (js/setTimeout resolve 10000)))
                     (then (fn [_]
                             (reset! render? true))))]
    (when @render?
      comp)))

p-himik17:02:26

r above is reagent.core. It works because the when-promise-resolved gets re-rendered when the value of render? is changed - because render? is derefed in the view function, which makes Reagent track its changes.

p-himik17:02:25

You can replace r/with-let with a form-2 component - the one you have in your example, only that render? and all the promise-related stuff will have to be in a let outside of the inner view function.

pez17:02:53

Thanks for wonderful help! I am wrapping a promise library so a promise only solution is perfecto. Can’t wait to test this!

👍 1
pez20:02:04

It works. Super happy I learnt about it. But I now realize I can't do it this way. I need to somehow stop re-render. I have a page rendered and need to defer re-render until the promise resolves....

p-himik20:02:52

...isn't that what my code does? Not sure what you mean.

pez21:02:01

It delays the first render and shows nothing while waiting. When the component rerenders there is no delay. Not sure how to explain my use case... I am probably going about it completely wrong.... The component in my case is the whole page. For certain route changes I need to do a fetch towards a server before re-rendering anything. I can create this wait in the router, but the app-db (re-frame) isn't updated yet there so I can't get the params I need for the server request. So I decided to try when page is about to re-render...

p-himik21:02:25

> re-frame Ah, you should've mentioned it before. :) You don't need promises at all. In fact, they make things a bit harder with re-frame. In your router, or wherever it's suitable in your app, set some :in-progress? true flag in app-db for the current page. In the page component, get the value of that flag, and if it's true then just display a progress indicator and don't render anything else. Make the route change that you need, update app-db in the way you need (except for the :in-progress? flag), fetch the required data - do everything that's needed for the page to work. And only then set :in-progress? false.

pez21:02:50

Yeah, should have mentioned re-frame. 😃 It sounds much less cludgy than all schemes I have deviced so far. However, I can't really blank out the page and display a spinner, I need the page to stay rendered in its previous state and then continue ones the server data is fetched. Is that also possible to achieve, you think? Maybe :should-component-update could be used?

p-himik22:02:35

> I need the page to stay rendered in its previous state Then you'll have to make sure that while all the data preparation and retrieval is taking place, the data for the current page that's already in app-db stays exactly the same. But you can still change app-db in a way that doesn't affect the page's data. And if you need for the current page to be non-interactive while requests are in flight, just put a transparent div over the whole page that would catch all mouse and keyboard events. Given what I currently understand from your descriptions, I don't think you'll need any React lifecycle methods. In fact, ideally you should not attach anything to rendering, unless it's related to the rendering itself. Views should be functions of your data - not the other way around.

pez22:02:02

Thanks again. Especially for your patience!

p-himik22:02:08

Any time. :)

Clement San11:02:38

Hello, i am trying to integrate reagent-material-ui within a luminus-created app. I got trouble loading the `@mui/x-data-grid package : compilation works fine but when loading the page i get : 

app.js:1455 TypeError: Cannot redefine property: ModalManager
    at Function.defineProperty (<anonymous>)
    at eval (index.js:2038:10)
    at Array.forEach (<anonymous>)
    at Object.shadow$provide.module$node_modules$$mui$material$node$index (index.js:2034:21)
    at shadow.js.jsRequire (js.js:66:18)
    at Object.shadow$provide.module$node_modules$$mui$x_data_grid$index_cjs (index-cjs.js:3:118)
    at Object.shadow.js.jsRequire (js.js:66:18)
    at Object.shadow.js.require (js.js:113:20)
    at eval (reagent_mui.x.data_grid.js:2:62)
    at eval (<anonymous>)
I put a reproducible example https://github.com/clementlefevre/luminus_reagent_material_ui OTH basic component like the reagent-mui.material.chip work fine. What i tried : • adjust the shadow-cljs.edn conf with
:compiler-options {:infer-externs :auto
                      :output-feature-set :es2018}
 :js-options       {:anon-fn-naming-policy :unmapped
                      :entry-keys ["module" "browser" "main"]}
Any help would be welcome ! Cheers, clement.

p-himik12:02:14

Can't run the project with lein run - getting

could not find a non empty configuration file to load. looked in the classpath (as a "resource") and on a file system via "conf" system property
There's cprop.core in the stacktrace. Looks like you have some necessary config file in your system that's not in the repo.

Clement San12:02:24

my deepest apologies, i forgot to push the config files. I just did a dry run and it should work now. Again thank you for your time.

p-himik15:02:31

It seems like a problem with MUI - their node/index.js blindly tries to override exports. It might be #shadow-cljs specific given that node/index.js is in the "main" entry key but you explicitly tell shadow-cljs to first try "module", so I'd definitely recommend asking in that channel as well. I tried removing "main" altogether and it lead to other issues. I now remember myself struggling with making MUI5 work soon after it came out, so I ended up staying on MUI4.

p-himik15:02:57

A couple of other things: • Don't use ^ in package.json (i.e. add flag -E when installing NPM packages) when you're writing an app and not a library - helps avoid potential issues when you install new packages • Don't explicitly include transitive dependencies in package.json, like that @emotion stuff - it makes it much harder to upgrade/change the dependencies later on

p-himik15:02:34

Another thing - make sure the versions of shadow-cljs in both project.clj and package.json are consistent. I've also tried updating shadow-cljs to the latest version in your project - same result. I'd say it's definitely worth asking in #shadow-cljs given how there was https://github.com/thheller/shadow-cljs/issues/970 that's been fixed.

zimablue15:02:15

(defn test-fn "doc-str" {:a 1} [{:keys [a b]}]  (println "a " a " b " b))
(meta test-fn)
returns nil, is this expected?

p-himik15:02:27

Try (meta #'test-fn).

zimablue15:02:09

that worked, thanks, clojurescript (or tbh clojure) vars baffle me a bit

James Amberger17:02:10

Hi, I’m going to start my first cljs/reagent app shortly. Is there a consensus on how to style your components? Back in JS world I used styled-components .

1
p-himik17:02:35

There doesn't seem to be a consensus. Some prefer Tailwind, some prefer some CSS-in-JS solutions, either CLJS ones or JS ones, some prefer Bootstrap, some use full-fledged frameworks like MUI with styling built-in, some prefer raw CSS,...

1
James Amberger17:02:46

Afraid you’d say that!

p-himik17:02:07

I can send a file with my notes on CSS in CLJS. It's mostly a bunch of links, but maybe it'll save you some googling.

p-himik18:02:28

Sure.

👍 3
James Amberger22:02:01

cljss looks cool

James Amberger22:02:40

kind of like js styled-components

dvingo22:02:53

you can also use styled components 🙂 https://github.com/dvingo/cljs-styled-components

👍 1
James Amberger01:02:23

so many choices…

Michaël Salihi22:02:07

Is there a way to get the default behavior of JS to compare 2 values without coercion as == does and not === in CLJS?

2 == "2" => true
2 === "2" => false

p-himik22:02:52

== in JS is coercive-= in CLJS. === in JS is identical? in CLJS.

👍 1
Michaël Salihi22:02:47

Nice, coercive-= is exactly what I ask for. 👍 Thansk @U2FRKM4TW!

👍 1