Fork me on GitHub
#clojurescript
<
2021-04-14
>
wombawomba12:04:14

I'm walking over a data structure that contains JS "classes" like #object[Boolean] and #object[String]. How can I detect if an object is such a "class"? In regular Clojure I'd just use class?, but that's not available in cljs.

manutter5112:04:17

cljs.user=> (type [])
cljs.core/PersistentVector
cljs.user=> (type #js [])
#object[Array]
cljs.user=> 

nilern13:04:08

cljs.user=> (instance? js/String (js/String.))
true
cljs.user=> (instance? js/String "")
false

😭 3
nilern13:04:57

JS is awesome

raspasov19:04:52

“The `String` constructor is used to create a new `String` object. When called instead as a function, it performs type conversion to a https://developer.mozilla.org/en-US/docs/Glossary/String, which is usually more useful.”

wombawomba13:04:22

I don't think that solves my problem though

wombawomba13:04:36

How do I tell if something is #object[Boolean] or #object[String] or #object[Number] etc, as compared to a concrete instance of one of those classes, or to a Clojure object?

p-himik13:04:58

Can you describe precisely what that "etc" means?

p-himik13:04:18

And what's the problem you're trying to solve in the first place?

wombawomba13:04:51

I have a data structure that contains a random assortment of java classes and regular values/objects, or the corresponding objects in javascript (i.e. what we get via (class foo)), and I need to tell the classes apart from the concrete values

wombawomba13:04:32

on the JVM this is trivial, because I can just use class?

wombawomba13:04:37

but I'm not sure how to do it in JS

p-himik13:04:20

Do you consider functions to be classes?

wombawomba13:04:44

if it's what's returned from class, then yes

wombawomba13:04:30

in practice I figure that this should be doable, because e.g. prn seems to be able to handle it fine

p-himik13:04:09

Well, you're out of luck, it seems. In JS functions are those "classes".

16:48:03.106 class X {};
16:48:03.108 undefined
16:48:07.154 X
16:48:07.156 λ[]
16:48:10.762 typeof X 
16:48:10.764 "function"
16:48:41.318 function Y() {};
16:48:41.321 undefined
16:48:46.524 y = new Y();
16:48:46.526 Y {}

p-himik13:04:17

If your values cannot be functions, then you can just filter with fn?.

wombawomba13:04:28

they can be functions too though

p-himik13:04:48

Welp. Unless you have a predefined set of "classes", I don't think you can solve that problem.

p-himik13:04:54

Or a predefined set of proper functions.

wombawomba13:04:58

I guess I'll just try to enumerate all the classes I can think of and do an equality check

wombawomba13:04:14

and cross my fingers that no other "classes" pop up

nilern14:04:04

There are only String, Boolean and Number

p-himik14:04:22

@U4MB6UKDL What do you mean? What would be a common description for those?

nilern14:04:12

Primitive wrappers

p-himik14:04:38

I might be wrong, but I didn't get an impression that @U15RYEQPJ was talking specifically about those. I'm willing to be corrected though. :)

nilern14:04:54

bigint and symbol are quite new, thus my omissions

Franco Gasperino15:04:54

What is the community recommendation on web component selection which supports react and is usable within clojurescript, preferably with the interopt abstracted away. I've tried both react-bootstrap and re-com with some success, but wanted a more informed feedback on direction

p-himik15:04:30

I'm pretty sure the answer is basically the same as it would be in the JS community in general. Interop is not that much of a problem, but of course depends on the environment in which you'll be using web components. E.g. Reagent does most of the work for you.

raspasov15:04:38

There’s also Helix, if you prefer a more direct React interop without much of state management.

Franco Gasperino15:04:18

right now, im appreciating the re-frame model for state management, but am ignorant on best practices for the widgets/components

raspasov15:04:52

Widgets/components as in “building your own” or “using existing” ones?

Franco Gasperino15:04:11

i'm hoping to leverage existing libraries

p-himik15:04:42

There are no best practices here, just like in JS. Some UI component libraries work for some people, some work for others. E.g. I chose Material UI specifically for two reasons - I like its documentation and its number input component is sensible (can parse scientific format).

raspasov15:04:14

Got it; You can just used them directly. I personally use my own judgement about the trade off between value provided and ability to change that component widget. Another question to ask is: “Is that component/widget at the right level of abstraction for the problem I’m solving?”

raspasov15:04:15

For example, I’ve seen people building collaboration software and basing a big part of their product on a text editor widget; When you hit the limits/bugs of that widget, you’re stuck with a pile of complex code that nobody understands.

raspasov15:04:54

On the other hand, picking a nice animation library that provides some fundamental animation abstractions can be a good choice.

nilern15:04:21

I prefer CSS-only widgets, less fighting

raspasov15:04:06

CSS-only? Most React components/widgets I’ve seen don’t fall in that category.

nilern15:04:39

Well just CSS -> no React

raspasov15:04:10

Got it 🙂 Yeah, haven’t done any development outside of React(Native) for a long time; can’t speak to that.

nilern15:04:52

It's been a while, I've used Bootstrap and Bulma but not a huge fan of those either

nilern15:04:37

I wanted to try https://getmdl.io/ at some time

raspasov15:04:44

I see. I wonder how much value something like Bootstrap provides nowadays given that flex-box is widely supported.

Franco Gasperino15:04:55

re-com is leveraging flex

Franco Gasperino15:04:13

however, it usses css styling from bootstrap

nilern15:04:19

In my mind widgets and layout are different feature sets

raspasov15:04:32

Taking a quick look at re-com… @U01RGC7177E have you used it?

Franco Gasperino16:04:28

i've tried a bit of my internal prototype with re-com & re-frame. It works well, but lacks some widgets (navigation bars) that would be helpful

Franco Gasperino16:04:17

i dont have any JS experience, so the removal of another tech stack simply for interopt is appreciated by me

raspasov16:04:11

I don’t have much production JS experience either; Most of modern React-specific JS is relatively understandable, at least on the surface level; Digging into complex libraries/widgets is another story.

Franco Gasperino16:04:15

are there any landmines which are known to the cljs community, specifically with regard to "avoid component library X", that I should know?

nilern16:04:47

In any case one should make sure that the widget library is a net benefit. I have heard things like Material UI are risky in that regard (inflexibility, issues with Reagent etc.)

raspasov16:04:46

@U01RGC7177E I don’t think there’s a list that says GRAB (generally recognized as bad) 😄

Franco Gasperino16:04:24

alright, i appreciate the feedback from the 3 of you

👍 3
raspasov16:04:29

Perhaps I should make one! 😝

raspasov16:04:01

generally = as by me

Franco Gasperino16:04:41

ill stick with re-com for now. it mostly stays out of my way. the core of the SPA will be https://github.com/projectstorm/react-diagrams , while the re-com will handle the UI navigation bits

Dave A18:04:33

@U01RGC7177E I'm so happy you posted that link to react-diagrams, hadn't heard of it and it's exactly what I was considering having to DIY implement a bit down the line 😵

Franco Gasperino18:04:58

I don't know how easy it will be to integrate. I've done a reagent/adapt-react-class call or two, but beyond that ...

jacekschae19:04:56

On the surface all of the React libraries will work via Reagent interop :> and hopefully you are using shadow-cljs to do the npm imports. Beneath the surface there is a lot to unpack. Picking a UI library is not as simple as it was when only Bootstrap was around. Since then so many things happen and frontend community experimented in so many ways that it’s hard to keep up.  In general there are a couple of things you can use, I’ll categorize them in the following way but it’s just my point of view, don’t consider this as a ultimate: React + CSS; these libraries require npm install of a library and adding a CSS file to your <head />. • https://material-ui.comhttps://ant.design/docs/react/introducehttps://react-bootstrap.github.iohttps://blueprintjs.comhttps://react.semantic-ui.com Tradeoffs: • It’s CSS so it’s global thing and after some time you will be wondering can I remove this class or is it used anywhere? React Components + CSS-in-JS; these libraries require only npm install and CSS is generated by JS/CLJS, this solves the Cons of the React + CSS, because your style lives together with the component and not in the global namespace (CSS file).  • https://evergreen.segment.comhttps://rebassjs.org • All other libraries based on https://styled-components.com or https://emotion.sh/docs/introduction The way this works is that styles are dynamically generated at request time. Some libraries generate styles at build time so you would have to check. Tradeoffs: • Dynamically generated styles, which means you throw away caching. • Styled components need additional nesting of classes and they generate additional wrappers for your React elements which bloats your DOM tree and slows down the React diffing since the more your have in your tree the longer it takes; compare Google Chrome Dev Tools Elements vs Components tabs. • You bundle size will be slightly bigger because each component now has CSS definitions, on the other hand you don’t have CSS file. • Writing CSS in JS is sometimes painful, they rely heavily on template literals and if you don’t use shadow-cljs which proves some additional facilities to deal with that, you will suffer sometimes. • You need to name almost every single component, and naming is hard. Reagent + Atomic CSS; this is somehow new way to work with CSS, you have utility classes, sort of abstraction over CSS syntax, and as you can imagine your initial bundle size is huge since most of the libraries have new names for CSS. To work around this there is a https://postcss.org plugin that does tree-shaking for your CSS (removes unused styles from your CSS, think Google Closure compiler for CSS). There is also a new approach in tailwind-css where you have a JIT compiler to generate classes that are used in your files — check out this setup https://github.com/jacekschae/shadow-cljs-tailwindcss if you are interested how to get this going. https://tachyons.io https://basscss.com http://buildwithbeard.com https://turretcss.com https://tailwindcss.com Tradeoffs: • You end up with pretty messy CSS classes, some people don’t mind, some people hate it. Hope that helps.

Franco Gasperino22:04:30

on the topic of best practices, what is the cljs equiv of slurp?

djblue23:04:21

Depends on the cljs runtime, fs/readFileSync for node

Franco Gasperino23:04:46

is there a generic approach to a browser-based runtime?

lilactown23:04:03

generally browsers do not have access to the file system

☝️ 6
Franco Gasperino23:04:05

in this use-case, i'm mocking out a json message which would be arriving to the runtime via an async call. I'm looking for a way to create the message locally for development.

djblue23:04:55

(.resolve js/Promise data) would be how I would mock an async call

💯 3
Franco Gasperino23:04:26

I dont need to mock the async bit, i need to mock the actual content. (-> (slurp) (js/JSON.parse) (js->clj) -> ... throw onto channel)

Franco Gasperino23:04:42

it's the slurp equiv i'm attempting to replicate for mocking

djblue23:04:03

If the content you are expecting from slurp is a json string, you could mock slurp with (constantly "<json string>")

Franco Gasperino23:04:29

makes sense. was looking for a way to maintain the string outside of the code for this case. I'm running a number of mock content inputs against a schema validation, and they're getting to be overbearing to define all as values in code

Franco Gasperino23:04:11

(def mock-element
  {:id (str (random-uuid))
   :name "mock-element-1"
   :description "This is a mock"
   :repo "configured-repo-1"
   :type :input
   :widget :square
   :config {}})

(defn edn->json [content]
  (-> content
      stringify-keys
      clj->js
      (js/JSON.stringify)))

(edn->json mock-element)

Franco Gasperino23:04:24

would like to move mock-element out to local file for testing

Franco Gasperino23:04:43

(granted what i pasted is the inverse of the question, but illustrative nonetheless)

Franco Gasperino23:04:00

(defn json->edn [content]
  (-> content 
      (js/JSON.parse)
      js->clj
      keywordize-keys))

lilactown23:04:05

this isn't possible to do at runtime because browsers, where your code is running, do not have access to the file system

lilactown23:04:38

there is a way to do this in shadow-cljs, but I don't really know that it's any better then keeping the EDN in a CLJS file?

Franco Gasperino23:04:09

The FileReader class is strictly for node (@U1G869VNV suggestion) or in-memory payloads from forms or blob arrays, then?

lilactown23:04:36

right, you can read in the file at compile time but the naive version will not reload on changes to those files. this can get annoying and complex if you change those files at all often.

💯 3
lilactown23:04:05

the reloading problem is solved by the shadow-cljs helper in the link I posted

lilactown23:04:36

still, if you are set on moving these outside of code that is the best approach

lilactown23:04:23

btw, portal is very very cool 🙂

❤️ 3
💯 3
Franco Gasperino23:04:39

thanks for the input