clojurescript

ClojureScript, a dialect of Clojure that compiles to JavaScript http://clojurescript.org | Currently at 1.10.879
Kannan Ramamoorthy 2021-05-27T03:33:50.314900Z

Team, Iโ€™m experimenting with Cljs with re-frame, smooth-ui. And kind of fiddling to get a simple UI component to show-up. It works on a particular case and not when I just wrapt it in a function. Posted the details in https://stackoverflow.com/questions/67715254/cljs-re-frame-is-not-showing-element-as-expected. Any guidance would be helpful.

Kannan Ramamoorthy 2021-05-27T18:22:10.344800Z

I have put [ in both the places. Still facing the same issue.

Kannan Ramamoorthy 2021-05-28T02:47:42.381Z

My bad, I also had to put the [ around the function call that just generates data.

(defn app
      []
       [grid [generate-data]])

Kannan Ramamoorthy 2021-05-28T02:48:56.381200Z

Thatโ€™s a huge help. Also I found the article very helpful.. Thanks a lot @dpsutton!

dpsutton 2021-05-27T05:00:48.315300Z

do [grid (generate-data)] and [cell {:id "1-1"...}]

dpsutton 2021-05-27T05:00:58.315500Z

square bracket [ versus a paren

Jacob Rosenzweig 2021-05-27T06:33:08.319200Z

Is there any reason why binding a r/atom in a reagent component wouldn't work?

lsenjov 2021-05-27T06:33:34.319500Z

What do you mean?

lsenjov 2021-05-27T06:33:47.319800Z

Are you meaning derefing it with @?

Jacob Rosenzweig 2021-05-27T06:36:05.320600Z

This works fine:

(defonce items (r/atom []))
(defonce text (r/atom ""))

(defn component [{:keys [title on-delete]}]
    {
     :pre [(s/valid? :component/title title)]
     :post [(s/valid? :component/title title)]
    }
    [styled/root 
     [styled/details
      [:span title]
      [styled/delete-button {:onClick on-delete} "Remove"]]
     [styled/submission {:onSubmit (fn [e] (.preventDefault e) (swap! text inc))}
      [:p (str @items)]
      [:p (str @text)]
      [:input {:type "text" :on-change (fn [e] (swap! text #(str e.target.value)))}]]])
But not this:
(defn component [{:keys [title on-delete]}]
    {
     :pre [(s/valid? :component/title title)]
     :post [(s/valid? :component/title title)]
    }
    (let [items (r/atom []) text (r/atom 0)]
    [styled/root 
     [styled/details
      [:span title]
      [styled/delete-button {:onClick on-delete} "Remove"]]
     [styled/submission {:onSubmit (fn [e] (.preventDefault e) (swap! text inc))}
      [:p (str @items)]
      [:p (str @text)]
      [:input {:type "text" :on-change (fn [e] (swap! text #(str e.target.value)))}]]]))

dpsutton 2021-05-27T13:27:46.331600Z

You need a barrier to not redefine the atoms on each render. They need to be defined once and then changed in the render

Jacob Rosenzweig 2021-05-27T06:40:41.320800Z

Oh I fixed it:

(defn component [{:keys [title on-delete]}]
    {
     :pre [(s/valid? :component/title title)]
     :post [(s/valid? :component/title title)]
    }
    (let [items (r/atom []) text (r/atom 0)]
      (fn [] 
        [styled/root 
         [styled/details
          [:span title]
          [styled/delete-button {:onClick on-delete} "Remove"]]
         [styled/submission {:onSubmit (fn [e] (.preventDefault e) (swap! text inc))}
          [:p (str @items)]
          [:p (str @text)]
          [:input {:type "text" :on-change (fn [e] (swap! text #(str e.target.value)))}]]])))

Jacob Rosenzweig 2021-05-27T06:41:27.321100Z

So apparently I needed to return a new function and not just the view layout

Jacob Rosenzweig 2021-05-27T06:41:49.321400Z

I wonder why it works that way.

2021-05-27T06:43:00.321600Z

You need something callable upon the change happening

Endre Bakken Stovner 2021-05-27T06:58:13.326300Z

Shadow-cljs has this rectangle that shows in the bottom of the screen when there are warnings in the code. I'd like to add one to my project too. What terms do I google to find instructions on how to create something similar? Or does anyone have an example code snippet I can play with?

thheller 2021-05-27T07:18:39.327600Z

@endrebak85 basic DOM stuff like animations, transitions, transforms. the code for the shadow-cljs stuff is here https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/cljs/devtools/client/hud.cljs#L54-L139

๐Ÿ‘ 1
thheller 2021-05-27T07:19:16.328300Z

but it is not react based. so a react/reagent based solution would look a little different

thheller 2021-05-27T07:20:30.329200Z

oh what I linked is the loading animation. if you actually just want the box at the bottom thats easier. just an absolute or fixed positioned div, https://github.com/thheller/shadow-cljs/blob/master/src/main/shadow/cljs/devtools/client/hud.cljs#L233-L237

Endre Bakken Stovner 2021-05-27T07:21:34.329600Z

I also wondered about that cool logo. If I ever have something that takes more than a second to render I'll use something similar.

lilactown 2021-05-27T17:24:15.337300Z

{:a (foo)
 :b (bar)
  ,,,}
if I were to call a function with this literal over and over again, could there be times when (foo) and (bar) calls swap order?

thheller 2021-05-27T17:27:21.337500Z

no, but the order they eval in might not be what you have in the code logically. if it reaches the hash-map threshold that is.

lilactown 2021-05-27T17:28:57.338200Z

๐Ÿ‘:skin-tone-2: I'm ok with that as long as it is stable

thheller 2021-05-27T17:30:10.338800Z

if you care about ordering maps are typically not a good idea. thats why bindings (eg. let) typically use vectors.

lilactown 2021-05-27T17:34:22.340300Z

in this case I was thinking through using React hooks in props maps, i.e.:

($ some-comp {:foo (use-foo) :bar (use-bar)})
and was trying to determine if I should write a rule in helix's linting to warn on usage

thheller 2021-05-27T17:36:23.341100Z

I think thats discouraged in react? too easy to end up in conditional branches (when stuff? ($ some-comp {:foo (use-foo) :bar (use-bar)})))

phronmophobic 2021-05-27T17:37:05.341600Z

If {:foo (use-foo) :bar (use-bar)} is always a literal map and $ is a macro, then you could write the macro to guarantee consistent* ordering

lilactown 2021-05-27T17:39:10.342400Z

@smith.adriane I'm not sure that's true since the map literal assigns its ordering at read-time, which is after the $ can get ahold of it

lilactown 2021-05-27T17:40:02.343400Z

@thheller somewhat true, but helix will detect you're calling a hook inside of a conditional and emit a compiler warning in that case

lilactown 2021-05-27T17:40:16.343600Z

JS linters also will call that out in the respective JS syntax

phronmophobic 2021-05-27T17:40:35.343800Z

the map literal must be read before it's passed to $, right?

lilactown 2021-05-27T17:40:44.344Z

right

phronmophobic 2021-05-27T17:48:07.344300Z

I might be confusing things, but I thought something similar to the following would guarantee consistent ordering

(defn with-order* [m]
  (let [body (into {}
                   (map (fn [[k v]]
                          `[~k ~(gensym)]))
                   m)
        bindings (into []
                       (comp (map (fn [[k v]]
                                    `[~(get body k) ~v]))
                             cat)
                       m)]
    `(let ~bindings
       ~body)))

(defmacro with-order [m]
  (with-order* m))

(macroexpand-1 '(with-order {:foo (use-foo) :bar (use-bar)}))
;; (clojure.core/let
;;  [G__44218 (use-foo) G__44219 (use-bar)]
;;  {:foo G__44218, :bar G__44219})

phronmophobic 2021-05-27T17:50:50.344500Z

I might even consider sorting the bindings by key to make bindings even more stable across time/compilations/versions of cljs

cpmcdaniel 2021-05-27T18:40:35.346500Z

I am trying to use with-redefs to mock a function. However, the function has multiple arities and only the first one is mocked. The original function gets called when the respective number of args are passed. Is this a known limitation of with-redefs?

cpmcdaniel 2021-05-27T18:49:41.346600Z

Hmm, a simple example works as expected

dnolen 2021-05-27T18:53:59.347Z

@cpmcdaniel doesn't seem possible?

dnolen 2021-05-27T18:55:04.347700Z

arities are on the fn itself - so when you redef the fn those won't be there

cpmcdaniel 2021-05-27T18:56:00.348400Z

I think somehow the original function value is getting closed over where it is being called with the second set of params

dnolen 2021-05-27T18:57:38.348700Z

also doesn't seem possible you need to create something minimal

dnolen 2021-05-27T18:57:56.349100Z

when you redef you are fully replacing the old thing - there's nothing to close over

Jacob Rosenzweig 2021-05-27T18:59:38.350700Z

How far does idiomatic clojure take the "just use a map" approach to function arguments? In React, JSX.Element is basically syntax sugar around a function which takes a map (`props`) as its input. But I'm not sure if I ever tried using just maps for any other part of my code (e.g. my service logic).

dnolen 2021-05-27T19:00:25.351Z

every Clojure system I ever worked on primarily used this pattern

dnolen 2021-05-27T19:00:48.351600Z

ClojureScript itself is like some 45,000 lines of code and is just EDN data + fns

Jacob Rosenzweig 2021-05-27T19:01:18.352600Z

I kind of get the thinking of it. You don't need type constructs like Options or Maybes to express optionality, just leave the key out of the map.

dnolen 2021-05-27T19:01:46.353300Z

there's some records + protocols lying around in ClojureScript from the old days but those are one of the less pleasant parts of the codebase now

Jacob Rosenzweig 2021-05-27T19:02:07.353800Z

Yeah I kept hearing "protocols + records" a lot, but I couldn't find anything modern on it that was still advocating it.

Jacob Rosenzweig 2021-05-27T19:02:14.354200Z

Seems like it was a trend, like core.type

dnolen 2021-05-27T19:02:34.354600Z

it is useful - but in very restricted circumstances

Jacob Rosenzweig 2021-05-27T19:02:59.355100Z

I like specs a lot more because you can specify a lot more than what ADTs can signify.

Jacob Rosenzweig 2021-05-27T19:03:12.355500Z

They're basically dependent types... except it's not checked at compile time obviously.

Jacob Rosenzweig 2021-05-27T19:03:35.356Z

But the problem with dependent types is that it gets really messy really quick. It's one reason why you don't see them in Haskell. They can't figure out a way to do it elegantly.

Jacob Rosenzweig 2021-05-27T19:05:03.356300Z

Can you link any clojure/script repo that you feel like presents good, modern, idiomatic Clojure? E.g. runtime polymorphism instead of protocols and records, maps instead of positional arguments, et cetera?

dnolen 2021-05-27T19:06:45.357700Z

specs are nice and work as advertised - we have two years of them - we've found it most useful in a system - i.e. client / servers - less certain about other cases

dnolen 2021-05-27T19:06:55.358Z

:pre and :post are also still quite useful

Jacob Rosenzweig 2021-05-27T19:08:57.359300Z

I'm using :pre and :post strictly for react components right now. Anything that I plan on reusing. And I use it sparingly, sometimes preferring default values.

dnolen 2021-05-27T19:10:03.360500Z

@rosenjcb I don't have anything off the top of my head, I think maps and collections scale quite well - you can say whatever you like about JS / React - but I think it has shown with taste and good engineering fns + simple data scales pretty dang well

dnolen 2021-05-27T19:10:16.360700Z

so this is not even specific to Clojure really

Jacob Rosenzweig 2021-05-27T19:10:47.361400Z

Honestly, JS has kind of teased me into some of the founding principles of Clojure.

Jacob Rosenzweig 2021-05-27T19:11:19.362400Z

I think you could easily convince a React developer that Clojure's ideas of maps and heterogenous collections, immutability is good.

dnolen 2021-05-27T19:11:23.362600Z

I came to Clojure from JS - basically Clojure had everything I like about JS minus the stuff I didn't like

dnolen 2021-05-27T19:11:36.362900Z

but that's the tip of the iceberg

dnolen 2021-05-27T19:12:27.363900Z

the only reasonably popular language carrying on the dream of real interactive development is Clojure(Script)

๐Ÿ‘ 1
2
Jacob Rosenzweig 2021-05-27T19:13:28.364600Z

I still don't have a good developer workflow with Clojurescript. Right now I just use shadowcljs + lein. It's not much different from my JS development, but Ive been wanting to see how I can do some repl based stuff as well.

dnolen 2021-05-27T19:14:54.366100Z

Cursive works well - on newer hardware like M1 IntelliJ is not slow - the other options I can't speak for since I generally avoid anything that cannot leverage clojure.main style REPLs

๐Ÿ‘† 1
Jacob Rosenzweig 2021-05-27T19:15:16.366400Z

Oh I just use vs code and vim.

dnolen 2021-05-27T19:15:50.367100Z

for UI stuff the text-editor integration is less annoying because of hot-reloading

Jacob Rosenzweig 2021-05-27T19:16:05.367400Z

One person suggests doing this:

write code as .cljc as much as possible including tests
evaluate it as if it were Clojure (using my current VS Code/Chlorine/Reveal setup)
use Figwheel Main for the cljs-into-the-browser aspect
re-frame, cljs-devtools

dnolen 2021-05-27T19:16:39.367800Z

that's another reasonable compromise

mauricio.szabo 2021-05-27T19:17:02.368300Z

Chlorine/Clover works really well with Shadow-CLJS, to be honest ๐Ÿ˜„

Jacob Rosenzweig 2021-05-27T19:17:29.369Z

I guess it allows you to keep logic separated from what's mostly view code. Since .cljc code is supposed to be "platform neutral", right?

mauricio.szabo 2021-05-27T19:17:31.369200Z

(But I'm the author, so maybe I'm biased ๐Ÿ˜„)

mauricio.szabo 2021-05-27T19:19:53.371Z

The problem is that not always that simple to just write "platform neutral" code on ClojureScript. JS runtime is async, and there's not "await" for it, so sometimes you need to do "promise dances" and other complicated things. On Chlorine/Clover, as it's 100% ClojureScript, I even wrote a version of "async matchers" that will timeout tests after a while, teardown, etc...

lilactown 2021-05-27T19:59:58.371400Z

Ah i see. Yes youโ€™re right

Joni Hiltunen 2021-05-27T20:05:27.372600Z

I think that with figwheel or shadow-cljs, the browser development is already pretty good. I use Emacs with CIDER but for clojurescript I think shadow-cljs hotreloading already does quite a lot

Joni Hiltunen 2021-05-27T20:07:34.373300Z

and karma for watching tests in a terminal... basically the setup you get with the re-frame template is amazing :D

cpmcdaniel 2021-05-27T20:20:59.373600Z

@dnolen I suspect my issue is related to the use of async code within the with-redefs block (specifically js/Promises). Is there another way to achieve mocking via :before and :after fixtures?

cpmcdaniel 2021-05-27T20:37:06.373800Z

OK, I was able to use set! in :before and :after fixtures to achieve what I wanted

dnolen 2021-05-27T20:56:22.374200Z

Thatโ€™s what I was about to suggest :)

dnolen 2021-05-27T20:56:41.374900Z

with-redefs isnโ€™t going to work with async code

Jacob Rosenzweig 2021-05-27T22:06:14.375100Z

Oh which tempalte is that? I use this https://github.com/reagent-project/reagent-frontend-template

Joni Hiltunen 2021-05-27T23:07:55.375600Z

@rosenjcb I use this for re-frame https://github.com/day8/re-frame-template

SxP 2021-05-27T23:19:55.379100Z

I'm running into a strange behavior with Clojurescript macros and the default build system, and I want to know if this is expected behavior or if I should file a bug. It seems like the default Clojurescript build system doesn't pick up changes to a .clj file that supplies macros. For example:

cat src/test/* && clj -M -m cljs.main --target node -c test.main && cat .cljs_node_repl/test/main.js && node .cljs_node_repl/main.js
;;;; macro.clj
(ns test.macro)
(defmacro libMac [] "test.macro/libMac 1")
;;;; main.cljs
(ns test.main (:require-macros [test.macro]))
(println "STARTING test.main")
(println (test.macro/libMac))


// Compiled by ClojureScript 1.10.773 {:target :nodejs}
goog.provide('test.main');
goog.require('cljs.core');
cljs.core.println.call(null,"STARTING test.main");
cljs.core.println.call(null,"test.macro/libMac 1");

//# sourceMappingURL=main.js.map
STARTING test.main
test.macro/libMac 1
This works as expected. But if I change macro.clj to "libMac 2" and run the same build command, the resulting main.js still says 1. I need to delete the main.js to get the system to rebuild it after each macro change.