Fork me on GitHub
#clojurescript
<
2022-12-20
>
Martin Mariano02:12:30

I am getting an error trying to import the latest version (or any version at all) of https://www.npmjs.com/package/react-aria into a cljs project. Is there something I can do to load them? Crashes the whole build. A simple: ["react-aria" :refer (useButton) doesn´t seem to work. shadow-cljs log: main.js:1426 SyntaxError: Unexpected token ',' at eval (<anonymous>) at goog.globalEval (main.js:472) at env.evalLoad (main.js:1534) at main.js:2137

thheller05:12:00

looks like the closure compiler generates some invalid code. unfortunately nothing I can do about that directly.

Martin Mariano12:12:51

thanks for taking a look at the issue @U05224H0W

Dallas Surewood03:12:26

I find clojurescript/rum to be way more complicated than using just React by itself (when making a SPA), but that may be because I first learned React when coding. Has anyone else had this experience? I like clojure, but I'm not seeing the value add of clojurescript yet. 1. Code sharing between frontend and backend has far fewer usecases than people suggest. Validation? Sure. But that's a pretty small part of what I write even on form heavy apps. Business logic? Maybe, but I'm not sure what business logic I would want exposed in my javascript. So the value add of "You can write the same language on frontend and backend" seems minimally helpful for what you lose by using an abstraction around javascript. 2. Translating javascript errors to what I need to do in Rum is hard. The compiled javascript isn't helpful to read. Thank god I know a lot about React, cause I think I would be pulling my hair out when I see things like "Value prop on input should not be null" and wondering which of my inputs they are even talking about. 3. Managing state has not, in my experience, been less difficult like people often say. Just trying to make a component with local state and an optional default results in this ungodly mix-in. I'm not sure how else to do this. This is for a TTRPG character add page, and it has optional defaults so it can be reused as an edit page.

(rum/defcs make-character-page
  < (rum/local
     #:kit.guestbook.spec.player-character
      {:name ""
       :stats
       #:kit.guestbook.spec.player-character {:str nil
                                              :dex nil
                                              :cha nil
                                              :hp nil}}
     ::character-sheet)
  < {:will-mount (fn [state]
                   (if-let [default-stat-values (nth (:run/args state) 3)]
                     (swap! (::character-sheet state) assoc :kit.guestbook.spec.player-character/stats
                            #:kit.guestbook.spec.player-character {:str (:kit.guestbook.spec.player-character/str default-stat-values)
                                                                   :dex (:kit.guestbook.spec.player-character/dex default-stat-values)
                                                                   :cha (:kit.guestbook.spec.player-character/cha default-stat-values)
                                                                   :hp (:kit.guestbook.spec.player-character/hp default-stat-values)})
                     state)
                   state)}
  [state on-back on-submit]
  (let [*character-sheet-state (::character-sheet state)]
    [:div.stack
     [:div "Make a character"]
     (character-sheet *character-sheet-state)
     [:div
      {:style {:display "flex"}}
      [:button.btn {:on-click on-back} "Back"]
      [:button.btn {:on-click (fn [e]
                                (.preventDefault e)
                                (on-submit @*character-sheet-state))} "Create"]]]))
How can I approach these things better?

thheller06:12:35

I don't know rum, so can't comment on any of that. but one thing you should definitely do is alias keywords. those long namespaces really make things difficult to read.

thheller06:12:03

:kit.guestbook.spec.player-character/default-stat-values could be ::spc/default-state-values or whatever alias you choose

thheller06:12:35

either via (:require [kit.guestbook.spec.player-character :as spc]) or (:require [kit.guestbook.spec.player-character :as-alias spc]) in the ns

thheller06:12:53

also why not just initialize with the default value in the rum/local if you a setting them anyways? the :will-mount looks to me like it can be completely skipped?

p-himik08:12:40

> Validation? Sure. But that's a pretty small part of what I write even on form heavy apps. And a pretty large part of what I write. :) Specs are fantastic to share, especially with all the extra tools they have. Apart from validation and business logic, there are "utility" things, like e.g. parsing a complex URL into a CLJ data structure. > Translating javascript errors to what I need to do in Rum is hard. The compiled javascript isn't helpful to read. Why do you need to read it?.. Perhaps Rum itself masks a lot of things from React - I don't know that. But I use Reagent and its usage of React is relatively straightforward. Apart from that, there are source maps - all JS errors in my browser that stem from some CLJS code actually point to that CLJS code, and my browser correctly navigates to it when I click on any of the stack trace links. > Managing state has not, in my experience, been less difficult like people often say. Again, no idea about Rum, but in general immutable-by-default data structures in CLJS are fantastic to work with. Not just from the code writing perspective, but also from the introspection one. Although those things might be prevalent in the JS world nowadays too - but they weren't just a few years ago. And state management in general has less to do with the language and more to do with the UI state framework/library that you're using. You also seem to be conflating using a very particular UI library in CLJS instead of a particular library in JS with using CLJS instead of JS. So it becomes a comparison of libraries and not a comparison of languages and phrases like "I'm not seeing the value add of clojurescript" lose their meaning.

Dallas Surewood08:12:48

@U05224H0W Didn't know you could alias keywords! thank you. rum/local, as a mixin, doesn't seem to be registering values from the args. it's not an outright error, it's just nil. Doesn't seem to be a way to do it, and there's no example in the rum docs that I can find. @U2FRKM4TW The extent of my use of specs has been just using malli to say "hey does this thing look like the right shape" Source maps have been a bit of a black box for me, so I didn't realize this was a workflow that was possible in clojurescript. I will look into setting that up. The state management library I'm using currently is similar to reframe. I'm using Citrus. It just happens that I wanted a local state component with default values and I didn't know how else to do it in Rum. It's not the type of thing I would keep track of in a global store or similar type thing. But I do need to keep track of it. I don't mean to condemn all of CLJS. I picked Rum because when I was starting this project, reagent hadn't been updated for over a year, and it seemed like a lot of people were really liking Rum. I couldn't tell you if I'm using it correctly, but I have used reagent a little and I think the differences between these libraries are small. My experience with them is they work fine until you need to do something specific that you could express very easily in React, and then you're stuck with obtuse code. I have been really diving into a very simple webapp with CLJ/CLJS for the past few months. My experience is that the backend is very fun but that I will never catch up to how productive I am in regular React on the frontend. And I would like to keep trying it if there's something at the end of the tunnel, but I also have noticed people evangelizing CLJS with react wrappers have somewhat outdated knowledge about the state of React. I don't really care one way or the other which one I use, I just want to make sure I actually know all the things CLJS can offer me before I just give up.

p-himik08:12:53

> reagent hadn't been updated for over a year It's not a bad thing though. :) In the CLJ world, it's pretty often that some things are not updated in years. Most of the time it means that they're stable or sometimes even complete. But it would be curious to see a representative project that does something that's easy in React but hard in CLJS with whatever library (Reagent would be my preference of course), in both JS and CLJS, so we could properly compare and maybe comment on both the JS and CLJS versions to either devise improvements to that project's code or maybe add some missing CLJS functionality to that library.

Dallas Surewood08:12:36

I'd be interested in doing that. Maybe when I finish this in CLJS I can make a react version and use the same backend

👍 1
Dallas Surewood09:12:42

But for now, unless there's something I'm missing, Rum is not able to make local state with default values in a compact way

p-himik09:12:27

What would a compact way look like in a React version of a similar component?

Dallas Surewood09:12:19

It would just be something like const [form, setForm] = React.useState({str: 12, dex: 13...}). You just pass the default data to useState if you want it. I'm surprised I can't do that with this mixin

p-himik09:12:00

In Reagent, you'd just use a ratom - it's 1 line to create it and 1 line to create a setter (often, you can create it inline right where you use it).

p-himik09:12:05

(def my-component []
  (r/with-let [form (r/atom {:str 12 ,:dex 13})]
    [:button {:on-click #(swap! form update :str inc)}
     "Increase STR from " (:str @form)]))

Dallas Surewood10:12:49

Yes I remember that being pretty simple in Reagent. I don't remember when I decided to switch to Rum. Part of it may have been the out of box SSR support, and part of it was that you can use state from anywhere instead of ratoms. It just so happens with this mixin, you're stuck with an atom

Dallas Surewood10:12:11

I guess I could just use a (let a (atom) here too. It will just need an if check saying "if this data exists, use it for the atom, otherwise use this." Another thing that's easier, IMO, is forms in general. React-hook-forms makes doing forms with validation and automatic error messages so easy. Right now, I haven't found a good pattern for this with Malli. I want to be able to display all the errors, highlighting the inputs before I even allow a network request. And I want it to work on edit forms and add forms. Edit: Actually, doing let atom in Rum won't work, because the way Rum reacts to mixins means it will be making a new atom on every render. It would have to be in a parent component and be passed down. And then that parent component would need to be in charge of emptying itself when we navigate away from the child component. Something you get for free when using local state in a component.

Albert11:12:16

rum could use react hooks, is it an option for you? you have to choose, since mixins and hooks could not mix together.

Dallas Surewood11:12:48

I guess I'm not sure yet. Not being able to use rum/reactive just because I want default local state seems like a problem.

Dallas Surewood11:12:12

From what I understand, that means the component has no way to rerender, unless manually remounted

dvingo16:12:16

I would highly recommend looking into https://github.com/lilactown/helix (a lightweight wrapper around react, designed for the react 18+ world) . For forms and validation I have been using react hook form via JS interop (example https://github.com/matterandvoid-space/todomvc-fulcro-subscriptions/blob/766d27be316c3f2ab6a23bd8db30932ec0601a4f/src/main/space/matterandvoid/todomvc/todo/ui.cljs#L32). I haven't tackled it yet, but plan to integrate malli validation with using a custom resolver which you should be able to do as well: https://react-hook-form.com/advanced-usage#CustomHookwithResolver

👍 1
kennytilton14:12:12

I agree, @U042LKM3WCW, that CLJS per se is not worth the interop hassles compared with straight JS, now that JS is growing up a bit. But then I am the resident mumbling old crank out on the porch in a rocking chair where I can be easily ignored...where was I? The CLJS (or ClojureDart!) win would be in finding a better framework than React, even if that framework has React under the hood: Lisp programmers do not just wrap X, they make X easier to program in the process. eg, re-frame does Flux/Redux better. And what are you using for app-wide state management? Redux? MobX original? This is the level at which CLJS/D might open better doors. Final thought: CLJS is done, and has been for years. No thrashing over ESn versions. Which is nice.

lilactown18:12:41

is anyone aware of libraries that wrap google's "incremental-dom" library? the only one I've found is https://github.com/christoph-frick/cljs-incremental-dom. I thought there were more

jwind18:12:58

Hey everyone, I’d love to make something with React Native and Clojurescript, but I’m struggling to find a good place to start amongst many approaches out there. Any suggestions?

👍 1
grav20:12:17

There's https://expo.dev, which I've used, and which definitely is React Native made easy. And then there's https://www.npmjs.com/package/create-expo-cljs-app, which I haven't used, but seems to be Expo + Cljs made easy :)

hadils12:12:26

Another possibility is to use Krell and Expo. I use Krell, Expo, reagent and re-frame. It is a great experience. https://github.com/vouch-opensource/krell https://github.com/joshuamiller/react-native-template-cljs-krell-storybook

Carl11:12:08

@UGNMGFJG3 does that template use expo? I’m interested in trying expo with krell as I have had issues with hot reloading when using expo and shadow together.

hadils14:12:58

Hi @U01TU6MLA69. You can use expo with it. You will have to edit the krell_index.js file to make it work, but it is straightforward.