Fork me on GitHub
#clojurescript
<
2018-05-08
>
kzeidler00:05:06

I have a vague question about CLJS + Reagent. Specifically I'm curious about the origins of the monolithic 'app-state' design pattern, where you have a global object whose state cascades through various conditional rendering paths belonging to a bunch of (otherwise stateless) application components. Where does that pattern come from? Is it a Reagent idiom?

mfikes00:05:50

Om had to have done that earlier, right?

kzeidler00:05:55

Oh quite possibly. I'm curious mainly if it has analogues in some sort of native Javascript framework? When I picked up Reagent a few years ago to build my first web app I was under the impression the 'app-state' atom was a React convention, but the React applications I've built/learned from have all been burdened with a troublesome amount of local state, this pointers, locally-scoped objects, etc.

kzeidler00:05:07

Which might have some practical utility, but it's strikingly different than the architecture of a Reagent app as I remember it.

mfikes00:05:02

That's an interesting historical question. Hmm: "because we always have the entire state of the UI in a single piece of data, we can trivially serialize all of the important app state" in http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs might imply that JavaScript approaches weren't yet doing that. But I'm speculating.

kzeidler00:05:03

Fascinating, it seems like Nolen is foreshadowing Redux a bit with the suggestion of pairing React/Om with a stateless data structure like mori

justinlee00:05:53

i’m not sure who was first, but there is this notion of the “flux” architecture, which is pretty widely used and is supposed to be medicine for poorly structured local state

justinlee00:05:24

redux is the most common example. there is also the mobx library that is a much closer analog to the reagent atom idea

mfikes00:05:27

One interesting historical twist is how Om helped popularize React in a non-trivial way.

kzeidler00:05:54

Is there a direct relationship between the "atom" object in a Reagent (cljs?) application and the props attribute of a React component? I ask mainly because they both seem to have the odd property that you can't directly traverse them, instead you have to be somewhat coy and use destructured local variables to inspect particular components of it

justinlee00:05:16

the direct answer is no. reagent atoms are just mutable pieces of data (you can put anything in them). reagent implements an observer pattern by watching derefs of atoms during render. are props really different in react? I always thought it was just a normal property on an object

kzeidler00:05:57

@lee.justin.m Thanks for suggesting mobx, this does indeed seem a lot closer to Reagent-y ideas about state management I sorely miss 🙂

justinlee00:05:51

be careful with it. because you can’t fully proxy arrays in javascript, you have to be very careful with it or it just won’t work. i also found it hard to debug when things went wrong.

kzeidler00:05:11

You might be right, I'm still shaky about the scope/stucture of props in React. It's not clear to me that they can be referenced outside the local scope unless bound to some public attribute though, and if I'm not mistaken it's an inherent property of React components?

kzeidler00:05:25

Hold on, I'm going to verify this

kzeidler00:05:05

Ah, props is just idiomatic for a component's parameter arguments. Which, right, are intrinsically scoped to that object

justinlee00:05:30

oh right so reagent maps positional arguments to props and props.children in a slightly funny way. positional args are mapped to props.children, unless the first argument is a map, in which case that map is props.

😮 4
kzeidler00:05:16

Let me think on that for a second. I'm sure there's a good reason for it...

justinlee00:05:45

i think it is because children are ordered. props are not.

justinlee00:05:02

but i just thought of that so it could be wrong 😛

justinlee00:05:39

also it looks right: [:div [:div "one"] [:div "two"]]

kzeidler00:05:00

Interesting, is the type alone what differentiates JS objects from HTML DOM elements? "If it's a map then it's data, otherwise render it?"

kzeidler00:05:22

(In a Reagent/CLJS context I mean)

kzeidler00:05:57

Oooh data-directed programming. How I've missed you

justinlee00:05:00

it works and you never have to think about it until you do a bunch of interop code and your brain goes 🤯

kzeidler00:05:45

Heh, is there a pattern you've found that's more robust in the face of messy, real-world interop constraints?

justinlee00:05:15

i’ve written down all the ones i know about in the interop doc on the /docs folder in reagent

kzeidler00:05:47

I've been attempting to use JS React to render some word vectors to an interactive webGL canvas, but I'm really struggling to make all the components work together. I didn't have this problem at all in Reagent, the state was so blissfully monolithic

kzeidler00:05:51

So I'm considering maybe switching back to Reagent, but having been away from CLJS for a minute, I'm interested to hear what's new and fashionable these days in the world of MVC architectures

kzeidler00:05:26

Or even archaic, dull, and useful

justinlee01:05:33

so redux is a pain because it is so verbose, but you can be productive quickly and it works and is easy to debug. i would say its closest analog is re-frame.

justinlee01:05:53

also dan abramov’s videos on egghead are great and his docs are great

kzeidler01:05:12

You anticipated exactly the reasons I've been apprehensive to use redux 🙂 It probably is quite close to what I'm looking for, though. Do React components still ordinarily have local state when paired with Redux, or is it more common to isolate the entire world state in the Redux Store object?

justinlee01:05:58

either. common advice is: does anything else need this state? then put it in the redux store. if it is truly local, then local setState is fine

kzeidler01:05:57

Also do you have any recommendations for Redux tutorials? I think some of my previous missteps have been due to getting bogged down in ceremonious optimization details like, uh, const ACTION_NUMBER_259 = "ACTION_NUMBER_259"

justinlee01:05:54

just follow the egghead videos and docs on the website itself. and embrace the verbosity. you’ll drive yourself mad if you don’t. it’s really not that bad and there is payoff in having code that is dead simple.

kzeidler01:05:57

Alright, you've convinced me to give it another shot. 🙂 It was an open question in my mind whether the verbosity of Redux is itself an argument for using Clojurescript since the ceremonial details specifically seem to be ways of staying sane in a world full of mutable objects

justinlee01:05:53

I mean yes, you should definitely use clojurescript 🙂 It’s way better. But if you just want to get productive in pure javascript, then react+redux is pretty straightforward and you’ll recognize lots of ideas from it.

kzeidler01:05:28

The only thing I find holding me back from using cljs is I'm sending and receiving a lot of server-side data and it's not particularly clear to me after looking at a few examples how to render an HTTP response in clojurescript => data. On the other hand I wonder if I understood core.async a little better...

justinlee01:05:51

I’m writing an app that is basically a crud over a RESTish api to a node-js backend. I settled on using the promesa library with cljs-ajax. So far so good. People seem to love core.async but I found its handling of exceptions to be hard to deal with, and, generally, it seems like overkill for the kind of normal crud operations that I do. If you are familiar with promises, you might find that combination easier to deal with.

justinlee01:05:22

Though I’m not sure quite what you mean when you say “render an HTTP response in clojurescript => data”

kzeidler01:05:13

So an example would be typeahead, where the input field is possessed of some onChange handler whose job is to send HTTP requests to a database API endpoint. It also has to have some corresponding method for receiving the response, rendering the response to the page, handling the event that the user selects an available option from the menu of suggestions, etc.

kzeidler01:05:50

Of course there are ready-to-wear components/libraries implementing a far better typeahead component than I could cobble together myself, but there are other more niche operations I would need to figure out on my own, like how to pass an array of floats received from the server to a webGL renderer

justinlee01:05:54

right so there are a ton of engineering tradeoffs with something like that, but I think the answer is: you do in reagent whatever you’d do in javascript. the only difference is that instead of calling setState or firing off a action in redux, you will just do a swap! on an atom

justinlee01:05:46

or if you are using re-frame you’ll instigate the co-effect interceptor or whatever crazy thing you do there 🙂

kzeidler01:05:00

ha! I've read the re-frame description of "nested state machines" which sounds similar to Reagent's architecture but I haven't had the chance to use it. What's the biggest difference vs. Reagent? There are interceptors?

nenadalm08:05:10

Hi. You can check this faq about why to use re-frame: https://github.com/Day8/re-frame/blob/master/docs/FAQs/DoINeedReFrame.md. I like it because I wouldn't know how to nicely structure my app.

justinlee01:05:47

it adds a layer of state management on top of reagent. it is more explicit and there is more machinery. people who build large apps swear by it. i am poking fun of it a bit because there is so much terminology that it introduces. i think you can get by with reagent along for quite some time, but i haven’t built a big app with re-frame so maybe i’m missing out

boris03:05:46

Is there a way to get the browser console log to show in a figwheel repl?

dpsutton03:05:08

Would prn work for you or does it have to be the console?

dpsutton03:05:26

But prn should go to both the repl and the console

gdanov17:05:41

what makes you think so? code executing in the browser by default is not instrumented to send the output back via the figwheel repl protocol

dpsutton17:05:25

i say that because i see the outputs of prn's in my figwheel repl in CIDER and in the browser. I see the results of both prn and cljs.pprint/pprint

gdanov18:05:15

that's true when you eval code. but let's say you have some event handler in the browser that prints something. do you see that printed in the repl as well?

boris03:05:06

Are there any facilities to enable that behavior? For instance a lower-level browser api?

Cooky Zed14:05:39

what does it mean by excluding def with :refer-clojure here in the cljs.spec code?

(ns cljs.spec.alpha
  (:refer-clojure :exclude [+ * and or cat def keys merge])
https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/spec/alpha.cljs

mfikes15:05:10

@cookyzed That's related to s/def. I've looked at it before but never developed a full appreciation, given that def is a special form.

wei17:05:14

What's the best way to package some cljs code for non-cljs consumption?

nenadalm17:05:52

You could build js and then publish it on npm (https://docs.npmjs.com/getting-started/publishing-npm-packages). Don't forget to export required variables if you use advanced compilation (https://clojurescript.org/reference/advanced-compilation#access-from-javascript)

cjsauer17:05:05

@U066TMAKS shadow-cljs is well-suited for creating NPM packages. Here's the relevant section in the user manual: https://shadow-cljs.github.io/docs/UsersGuide.html#target-npm-module

cjsauer17:05:27

There's even a channel for it at #shadow-cljs

wei18:05:00

great, thanks for the suggestions!

Casey17:05:35

Any cursive + cljs users around? How do you run tests? I've just been using lein doo but the output is very difficult to parse. And now that I'm getting spec errors, its very very unreadable

nenadalm18:05:09

I've made pr into doo to use humane-test-output which should do better reporting: https://github.com/bensu/doo/pull/176. Until that's merged, you can use [org.clojars.nenadalm/lein-doo "0.1.11-SNAPSHOT"] (https://clojars.org/org.clojars.nenadalm/lein-doo). I never tried to run test in any editor (using Emacs).

Casey05:05:44

Thanks! The humane-test-output readme says not to use it with cider > 0.10

Casey05:05:54

What's up with that?

Casey10:05:55

@U662GKS3F I added your lein-doo snapshot.. but my test output looks the same in the console

Casey11:05:13

I spoke to soon! It is working now.

👍 4
nenadalm17:05:46

I have no idea what's the problem with newer cider (I use it just for src/ not test/).

Casey18:05:17

Hm, so i'll open that question up to any editors... how do ya'll view your test output?

Casey18:05:52

that is: (not (= <very big data structure> <other very big data structure>))

Casey19:05:13

@dnolen first I heard of it! Thanks for the link. Will take it for a spin.