Fork me on GitHub
#clojurescript
<
2018-02-22
>
Tim Wade01:02:05

Are there any general recommendations for clojurescript that might unavoidably have to deal with interop with a lot of stateful/mutable objects? I just started building a new reagent project that is coupled to the web audio API, essentially building a synth in JavaScript. But it already seems like a bad fit, unless there are patterns for dealing with this?

justinlee01:02:54

@hello721 my offhand thinking is that client-side webpages are inherently dealing with a stateful dom, so in a sense that’s a pattern

Tim Wade01:02:43

@lee.justin.m thanks for your thoughts! My pain at this point is dealing with reagent atoms. I'm keeping my stateful stuff in one, but because the objects I'm storing in there don't change, but their data does, things don't get updated/rendered properly. Perhaps that's a bad fit, and I'd be better off with plain ol' clojurescript?

justinlee01:02:50

so you’re storing javascript objects in reagent and then something on the javascript side goes and mutates the javascript object?

Tim Wade01:02:00

Correct....ooh am I not wrapping that change in a swap!?

justinlee01:02:46

yea reagent won’t know about changes to the atom unless you use swap! or reset!. so you just need to make sure whatever callback does the mutation does it the right way

Tim Wade01:02:47

Ah thanks! I think I got confused because the state changed, so I assumed it was working!

justinlee01:02:54

reagent provides its own implementations of those functions (which is why you have to use a reagent/atom and not a normal clojure atom). they (and deref) do all the dirty work of setting watchers and updating components

Tim Wade01:02:26

Ah thanks for kindly answering my n00b question! I guess I'm fortunate in that I've managed to avoid dealing with state in Clojure up to now 🙂 🙇

flyboarder04:02:50

seems I am still having macro issues, if I am passed a list of symbols as a input to my macro, how do I resolve them to the same namespace as the macro is defined in, not the one it is called in?

flyboarder04:02:05

Is there a list of macro variable resolution examples?

sd05:02:51

anyone have a moment to help me debug something with reagent?

justinlee05:02:01

@shib.duman sure. there’s also #reagent if it is really reagent specific (I’m in there too)

sd05:02:28

@lee.justin.m thanks for that, didn't see the channel for some reason

sd05:02:58

if i may ask, i'm using bulma with reagent as my css and i'm just trying to create a tab set that actually activates the "active" state when clicking on the men or women tab shown here:

sd05:02:33

for whatever reason, the class isn't being updated on clicking on men or women, confused about why

sd05:02:55

also "atom" in this case is reagent/atom

justinlee05:02:09

you need a wrapping function between the let and the first :ul

justinlee05:02:29

(defn … [group] (let [ … ] (fn [group] [:ul…)

justinlee05:02:56

the way you wrote it the atoms are rebound on every render

sd05:02:37

is there a reason i'd need a wrapper function? this is essentially returning a reagent element that's mostly just hiccup stuff. i would assume that whatever comes out of the (let) section would just be acceptable.

sd05:02:51

in any case, i've added a wrapper function and now it's complaining about: TypeError: Cannot read property 'call' of undefined

justinlee05:02:23

it should look like

(defn men-women-tabs
  [group]
  (let [men-active-tab (atom "is-active")
        women-active-tab (atom "")]
    (fn [group]
      [:ul {:class "tabs"}
       [:li {:class (str @men-active-tab)
             :on-click #(do (reset! men-active-tab "is-active") (reset! women-active-tab ""))}
        [:a "Men"]]
       [:li {:class (str @women-active-tab)
             :on-click #(do (reset! men-active-tab "") (reset! women-active-tab "is-active"))}
        [:a "Women"]]])))

sd05:02:04

that's exactly what i've got

justinlee05:02:42

you definitely need the wrapper. if you don’t write the wrapper, reagent will turn your original function into the render function of the react component, so every time the atom is updated, the original function is called. that original function promptly rebinds the atom

justinlee05:02:06

with the wrapper, the inner function gets turned into the render function, so the atoms don’t get rebound whenever they change

justinlee05:02:44

i’m not sure what your error is, however. this is the standard form for when you need local state. see https://github.com/reagent-project/reagent/blob/master/docs/CreatingReagentComponents.md#form-2--a-function-returning-a-function

sd05:02:37

so after fighting parinfer in atom i've got all the errors removed, but now instead of displaying markup like i wanted it to the function isn't displaying anything

sd05:02:22

i think however this is more an issue with my understanding of how reagent works as typically i've just been creating functions that return the appropriate hiccup trees (with methods attached if needed) in order to compose the dom

sd05:02:11

something like

sd05:02:22

in any case, i really appreciate the help @lee.justin.m

justinlee05:02:51

one thing: put your custom components in square brackets. you’ll get yourself into all kind of confusing situations if you don’t

justinlee05:02:05

so that should be [:div [functionB]]

sd05:02:07

i typically wrap them in a div or something

justinlee05:02:21

yea but not [:div (functionB)]

justinlee05:02:27

that’s not what you want

justinlee05:02:53

you want to return data and let reagent do all of the function calling

justinlee05:02:25

confusingly it’ll work 90% of the time 🙂

sd05:02:58

i must be in the 10% as i just wrapped a map call in that when it was calling the men-women tabs function and now it's throwing an error again

justinlee05:02:43

so if you want to call map, then you’ll call map with parenthesis, but when you use a component, you use [] so that reagent can call the component at the appropriate time

justinlee05:02:05

so like [:div (map (fn [something] [my-component something]) data)] <-- see how we actually invoke map but return a vector from inside

sd05:02:25

wrapping the "my-component something" in brackets did it

sd05:02:31

works perfectly now

sd05:02:52

so it's [(men-women-tabs args)] within that map function

justinlee05:02:47

great. I think the lesson of form-2 components and () vs. [] are probably the most subtle things about reagent.

sd05:02:04

is that well documented somewhere?

justinlee05:02:41

the link i just sent you is a good explanation of the 3 different types of components. this is a way of understanding the () / [] thing https://github.com/reagent-project/reagent/blob/master/docs/UsingSquareBracketsInsteadOfParens.md

justinlee05:02:00

i’m trying to write a user manual but it hasn’t been incorporated into the project yet. this is way i think about it https://gist.github.com/jmlsf/17c588deb326e538dcea6847bc66db9b#how-reagent-renders

sd05:02:39

this is awesome, thanks so much

roman01la14:02:00

are you building for Node.js?

yorggen14:02:35

no, i was just testing a snippet in lumo

yorggen14:02:50

it is supposed to run on browsers

joelsanchez14:02:26

cljs.user=> (defmulti test-multi count)
#'cljs.user/test-multi
cljs.user=> (def ^:export test-multi-exported test-multi)
nil

joelsanchez14:02:46

(find a better name, though)

yorggen14:02:10

great, thanks!

joelsanchez14:02:08

a common idiom is to use an asterisk for internal things/implementation details, like test-multi*

justinlee15:02:03

Just out of curiosity why the increased interest in lumo? I know what it is but I haven’t figured out why it is better to have a self hosted environment

joelsanchez15:02:55

I use it because it's a fast cljs repl for messing around and trying code

tianshu15:02:27

If I have a cljs lib A has npm-deps {B "0.0.1"}, is it possible to make project C that depends on A, also depends on B without declare in project.clj.

roman01la15:02:44

yes, you have to have deps.cljs on classpath

tianshu15:02:10

is this relevant to npm-deps?

thheller15:02:58

yes, deps.cljs with this content {:npm-deps {"B" "0.0.1"}}

tianshu15:02:09

@U05224H0W btw, does this work in shadow-cljs?

thheller15:02:03

shadow-cljs npm-deps to explicitly trigger it

thheller15:02:12

automatically done on server startup

tianshu15:02:45

thanks for reply!

justinlee16:02:35

I’m writing a longish reagent component (i.e. a function) that is “object oriented” in the sense that I want local shared state between a bunch of related “methods”. right now i’m just writing them in a big let statement. it works, but i was wondering if is there a more idiomatic way to do this

mccraigmccraig16:02:53

if i understand you correctly @lee.justin.m re-frame subscriptions will give you that, or you could split out components which do the actual rendering and pass them the state they needs as props (from your let)

justinlee16:02:15

i’m not using re-frame. too complicated for me.

justinlee16:02:40

yea okay passing state is the other option but i didn’t want to go there. just want to make sure i’m not missing something

hlolli17:02:42

@lee.justin.m watch out for local states in let, if you rerender those component, the local state will also reset. I'd limit local state to something like input form text value etc.

hlolli17:02:55

but each to their own style

justinlee17:02:33

well if you are doing something like manipulating a canvas, you need local state and you also want it to be reset when the component is freshly mounted

kurt-o-sys18:02:16

I'm reading strings using cljs.reader/read-string for reading tag literals. However, plain strings are handles somewhat unexpected:

(cljs.reader/read-string "hey you")
hey
why is it that read-string doesn't return the full string?

noisesmith18:02:51

it doesn’t even return a string - it reads the first full readable item in the string

noisesmith18:02:26

@kurt-o-sys a popular hack is (read-string (str "[" s "]")) which returns a vector of every readable item in the string

kurt-o-sys18:02:52

oh... right. So, I could try that hack, or add a tag literal #str or so?

noisesmith19:02:15

if you have a string, and want a string - why do any reading? just use the string

noisesmith19:02:21

maybe I’m missing something

kurt-o-sys19:02:31

the point is, I'm parsing a bunch of serialized data

kurt-o-sys19:02:42

some of them are plain strings, some of them are tagged.

noisesmith19:02:02

might I suggest a better serializing system? transit is great

kurt-o-sys19:02:21

yeah, will check. I thought using read-string would be more straight forward when it comes to reading plain strings 🙂

ag19:02:47

how do I pprint with output that cljs-dev-tools understands? custom formatter thingy?

ag19:02:32

if I do (cljs.pprint/pprint foo) it simply pretty-prints it as regular console.log info output. I want it the way so cljs-dev-tools make it expandable/collapsible kind of thing.

joelsanchez19:02:48

@ag js/console.log directly

ag19:02:53

oh... hmm... so why none of the clojure ones work that way? prn, println etc.?

ag19:02:10

anyway, thanks @joelsanchez

joelsanchez19:02:43

js/console.log-ing a clj data structure directly without cljs-devtools will result in an object full of cljs internal properties

joelsanchez19:02:56

that's why it's not done normally, only in the context of cljs-devtools

kaosko19:02:56

so I'm using persistent data.avl maps but I need to transit them over to the server. transit does not natively know how to serialize them. is there a better way to deal with this than manually invoke transient on them before serializing?

kaosko19:02:29

ah here: http://blog.cognitect.com/blog/2015/9/10/extending-transit. the official docs seem very light on this

mikethompson19:02:33

@ag pprint outputs a string. If, instead, you use js/console.log you will output the actual cljs data structure and that's when clj-devtools kicks into action. A string will remain a string.

kurt-o-sys19:02:20

I'm trying to add tags, like e.g. #location. It works when I do it like this:

(cljs.reader/read-string
   {:readers {'location tag-location}}
   string)

kurt-o-sys19:02:55

however, I can't seem to register/add the tag so I can use it in my code like this: (def a #location {...})

kurt-o-sys19:02:11

I have tried: (cljs.reader/register-tag-parser! 'location tag-location) (cljs.reader/add-data-readers {'location tag-location}) (cljs.reader/register-default-tag-parser! 'location tag-location)

noisesmith19:02:23

yeah, extending readers at runtime is a pain - this is part of what makes transit good

kurt-o-sys19:02:36

all give No reader function for tag

kurt-o-sys19:02:22

yeah. I understand that, but for now and I will have to check transit more in detail. can the readers be extended at compile time? (it doesn't matter to me in which phase they are extended, I just wonder how to add new tag readers that can be used run-time)

noisesmith19:02:34

with transit? they are taken as an argument to the read function

kurt-o-sys19:02:41

not with transit, but well, I guess transit will be the one that I have to use. Reading is not really the issue anyway. I just wanted to use things like #location {...} in my code. But #location is not recognized.

kaosko20:02:06

argh transit handlers in clojurescript - why do I get: (cognitect.transit/write (cognitect.transit/writer :json {:handlers {clojure.data.avl.AVLMap avl-map-write-handler}}) (clojure.data.avl/sorted-map :id "huh")) #object[TypeError TypeError: self__.tag_fn.call is not a function]

kaosko20:02:23

same works in clj: (let [out (java.io.ByteArrayOutputStream. 4000)] (cognitect.transit/write (cognitect.transit/writer out :json {:handlers {clojure.data.avl.AVLMap avl-map-write-handler}}) (avl/sorted-map :id "hello")) (.toString out))

kaosko20:02:58

(def avl-map-write-handler (t/write-handler "avl-map" (fn [o] (into (sorted-map) o))))

kaosko20:02:00

the error message is actually fairly clear but the blog post happily passes a string, however cljs implementation requires the tag to be a function instead. so this works: (def avl-map-write-handler (t/write-handler (constantly "avl-map") (fn [o] (into (sorted-map) o))))

JJ23:02:03

Probably obvious and dumb question, but I'm guessing the primary users of cljs are clojure users correct? I don't see the benefit of cljs if you don't have a clojure backend now that there is es6+.

souenzzo23:02:46

I'm a clojure/datomic developer I'm doing a re-frame app with a golang backend. clojurescript does not target ES6. It target's google closure compiler.

JJ23:02:42

yeah, I know that, what I mean is that ES6+ improved things a lot for JS. So JS today is pretty decent.

holyjak12:02:22

Yes but cljs is better AND has awesome , built in immutable data structures AND repl = much better than es6 IMO

souenzzo14:02:49

REPL and java interop/code reuse. Clojure is awesome.

justinlee23:02:08

no i don’t use clojure. i have a node backend and a cljs frontend

justinlee23:02:25

cljs effectively has full proxies, which is years away from landing in js if ever. that enables super convenient state management. the expressiveness of specter does all of that spread operator nonsense really easily. immutable data structures makes all of those redux optimizations happen for free

JJ23:02:20

@lee.justin.m what are full proxies?

justinlee23:02:39

what i mean is that there is a level of indirection in clojurescript datastructures which enables things like reagent’s atom technique to work perfectly. it’s pretty flakey in javascript because of the way arrays work (see the mobx library for an example of a library that tries to do this).

JJ23:02:27

@lee.justin.m you use cljs in the node backend too?

justinlee23:02:36

no. javascript.

justinlee23:02:48

partly that’s because of the other libraries and tools i’m using. it’d be nice to do it in clojure but my backend isn’t super complicated and node works and is fast and i don’t want to learn another effing way to deal with sql

JJ23:02:33

so you really only see the benefit of cljs in the browser and not much in node?

justinlee23:02:18

i’m using sequelize, a bunch of pdf parsing crap, and, unfortunately, sequelize/postgres (and also elasticsearch). cost/benefit is not there. also, i hate java and its entire ecosystem with the passion of a thousand burning suns. 🙂

justinlee23:02:54

also node + cljs is even less mainstream than cljs is to begin with so i don’t even want to know what the tooling is like

JJ23:02:55

ok, fair enough 🙂

justinlee23:02:00

but i don’t regret for one second leaving behind all that redux boilerplate (and all of the flowtype I had to use to make it manageable)

JJ23:02:29

are you using re-frame?

justinlee23:02:25

no. those frameworks are way over-engineered for my needs. maybe it makes sense if you have 10 people working on a project. not sure. probably helpful to use one as a learning project because it can help you structure your code. but i find it much easier to do things without the crazy complexity of re-frame. I keep all my state in a big map and have mutators that wrap specter and always run synchronously. then i have an “rpc” module that does asynchronous stuff with server calls and updates state once its done. this basically replicates redux’s actions/reducers without boilerplate.

justinlee23:02:54

you really better need that if you’re going to go down that path

JJ00:02:45

yeah, seems like framework for big apps with a team I guess

JJ00:02:53

have you try rum?

justinlee00:02:23

I did try rum. I liked it actually, but I could not figure out how to do interop with react libraries, and basically there’s one guy in an asian timezone who knows what the hell is going on with that library, so that wasn’t working for me.

justinlee00:02:46

but the differences with react are really ergonomic. they both provide roughly the same offering.

holyjak12:02:57

I am happy with Cljs on Node for AWS Lamda though prefer Clojure on jvm for a backend

mathpunk17:02:06

@U0522TWDA That's interesting. Can you point to an example of the configuration you use for cljsnode on Lambda?

holyjak17:02:23

No, it isnt public. What do you mean by “configuration”? I use serverless-cljs and custom :cljsbuild config with :advanced (would love to try #shadow-cljs when I have time)

mathpunk17:02:24

"serverless-cljs" isn't something I was aware of, so that helps, thank you

JJ23:02:35

I mean, can't you just use those node libs from cljs?