Fork me on GitHub
#clojurescript
<
2021-05-27
>
Kannan Ramamoorthy03:05:50

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.

dpsutton05:05:48

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

dpsutton05:05:58

square bracket [ versus a paren

Kannan Ramamoorthy18:05:10

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

Kannan Ramamoorthy02:05:42

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

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

Kannan Ramamoorthy02:05:56

That’s a huge help. Also I found the article very helpful.. Thanks a lot @U11BV7MTK!

Jacob Rosenzweig06:05:08

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

lsenjov06:05:34

What do you mean?

lsenjov06:05:47

Are you meaning derefing it with @?

Jacob Rosenzweig06:05:05

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

Jacob Rosenzweig06:05:41

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 Rosenzweig06:05:27

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

Jacob Rosenzweig06:05:49

I wonder why it works that way.

hkjels06:05:00

You need something callable upon the change happening

dpsutton13:05:46

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

Endre Bakken Stovner06:05:13

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?

thheller07:05:39

@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

👍 3
thheller07:05:16

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

thheller07:05:30

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 Stovner07:05:34

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.

lilactown17:05:15

{: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?

thheller17:05:21

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.

lilactown17:05:57

👍:skin-tone-2: I'm ok with that as long as it is stable

thheller17:05:10

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

lilactown17:05:22

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

thheller17:05:23

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

phronmophobic17:05:05

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

lilactown17:05:10

@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

phronmophobic17:05:35

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

phronmophobic17:05:07

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

phronmophobic17:05:50

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

lilactown19:05:58

Ah i see. Yes you’re right

lilactown17:05:02

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

lilactown17:05:16

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

cpmcdaniel18:05:35

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?

cpmcdaniel18:05:41

Hmm, a simple example works as expected

cpmcdaniel20:05:59

@U050B88UR 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?

cpmcdaniel20:05:06

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

dnolen20:05:22

That’s what I was about to suggest :)

dnolen20:05:41

with-redefs isn’t going to work with async code

dnolen18:05:59

@cpmcdaniel doesn't seem possible?

dnolen18:05:04

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

cpmcdaniel18:05:00

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

dnolen18:05:38

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

dnolen18:05:56

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

Jacob Rosenzweig18:05:38

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

dnolen19:05:25

every Clojure system I ever worked on primarily used this pattern

Jacob Rosenzweig19:05:03

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?

dnolen19:05:48

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

Jacob Rosenzweig19:05:18

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.

dnolen19:05:46

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 Rosenzweig19:05:07

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

Jacob Rosenzweig19:05:14

Seems like it was a trend, like core.type

dnolen19:05:34

it is useful - but in very restricted circumstances

Jacob Rosenzweig19:05:59

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

Jacob Rosenzweig19:05:12

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

Jacob Rosenzweig19:05:35

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.

dnolen19:05:45

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

dnolen19:05:55

:pre and :post are also still quite useful

Jacob Rosenzweig19:05:57

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.

dnolen19:05:03

@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

dnolen19:05:16

so this is not even specific to Clojure really

Jacob Rosenzweig19:05:47

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

Jacob Rosenzweig19:05:19

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

dnolen19:05:23

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

dnolen19:05:36

but that's the tip of the iceberg

dnolen19:05:27

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

👍 3
yes 6
Jacob Rosenzweig19:05:28

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.

dnolen19:05:54

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

👆 3
Jacob Rosenzweig19:05:16

Oh I just use vs code and vim.

dnolen19:05:50

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

Jacob Rosenzweig19:05:05

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

dnolen19:05:39

that's another reasonable compromise

mauricio.szabo19:05:02

Chlorine/Clover works really well with Shadow-CLJS, to be honest 😄

Jacob Rosenzweig19:05:29

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.szabo19:05:31

(But I'm the author, so maybe I'm biased 😄)

mauricio.szabo19:05:53

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

Joni Hiltunen20:05:27

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 Hiltunen20:05:34

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

SxP23:05:55

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.