This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
- # announcements (4)
- # aws (1)
- # beginners (60)
- # calva (10)
- # cider (21)
- # clj-kondo (38)
- # cljdoc (1)
- # clojure (59)
- # clojure-austin (1)
- # clojure-conj (1)
- # clojure-europe (19)
- # clojure-italy (9)
- # clojure-nl (29)
- # clojure-spec (6)
- # clojure-uk (85)
- # clojurescript (54)
- # community-development (11)
- # core-async (2)
- # cursive (21)
- # data-science (4)
- # datomic (39)
- # events (2)
- # fulcro (48)
- # funcool (1)
- # graalvm (5)
- # jackdaw (9)
- # kaocha (17)
- # luminus (2)
- # off-topic (10)
- # parinfer (22)
- # quil (1)
- # re-frame (4)
- # reagent (29)
- # shadow-cljs (7)
- # sql (9)
- # tools-deps (31)
- # yada (1)
@pt.roterski there was a tech glitch and it didn’t get recorded. It got re-accepted at the conj
What’s the idiomatic way of hooking up side-effects to the app state in Fulcro? For example, whenever a subset of the state changes, storing it as a URL fragment or in a cookie or things like this. In Reagent, I would hook up a listener to a cursor, in re-frame, hook up a listener to a level-3 subscription (both which are guaranteed not to fire the listener unless the particular data they refer to is changed). I.e., what I’m looking for is to decomplect arbitrary effects (including, possibly, talking to the server) happening as a result of state changing, and the mutations that caused the state to change (It might be that I’m jumping the gun here, I’m about 11 videos into the tutorials, which are fantastic btw)
@henrik That’s generally not the way you structure Fulcro apps. Changes (side effects) happen through the mutation (or state machine) system. Those are already abstractions around the concepts you’re typically concerned with, in which logic can be placed (and which are ultimately more easily visible and traceable). The component queries are how you “watch for effects”. Fulcro refreshes any component that queries for data that has changed. In the rare case that you have a need to watch state (for example, you can certainly put a watch on the state atom. If you wanted to combine that with some kind of triggering system that only triggered on specific data changes, then the normalized db makes that trivial to implement. Your examples: “storing it as a URL fragment”: there is no need to watch “state” for this. Routing is a global concern: the mutations that “route” can do this side effecting inline..why complect it with events? Same with storing something in a cookie: when you call the mutation that makes the interesting change, why not do the concern of persistence there, where it matters? The power of event systems is that you can connect into them at “some later time” without having to have the source of the original thing. In front end apps where you control the source of the entire app, this is not a core benefit (it’s great for OO systems where you want to hook into a pre-written windowing system, or DOM event system, etc.). The down-side of event systems is that when you write your application with them things become difficult to trace (requiring external runtime tooling instead of just simple “jump to” IDE features), hard to reason about and control, and “surprising” in undesirable ways. Fulcro only encourages event systems where they are usually needed: at the DOM or in an organized structure of a (small and self-contained) state machine.
Thanks for the comprehensive answer! I think this will illuminate itself as I start understanding the model properly. I think I did jump the gun a bit. Also, I realize that routing was probably not the best example, it turns out you handle that particular can of murderous snakes in another video. I have a rather complex piece of machinery (a search bar if you would believe it) that I’ve implemented and reimplemented in Reagent and Re-frame to study the differences. About 900 LOCs in both, and this is something that has struck me while working with them. I’ll use a metaphor. Say you take a pitcher of water and pour it on your cat. Your cat gets wet (and upset). The next day, you use a pot to pour water on the now rightly suspicious cat. It gets wet and upset. The day after that, you spray your cat with a hose. The cat gets wet and upset. The wetness and upsettedness(?) of your annoyed cat has nothing to do with your method of water delivery. Nor would it have anything to do with you as an actor really; if you hired someone to do it for you, the end result would be the same. Using a listener on the state is not idiomatic in neither Reagent, nor Re-frame, but I can’t shake the feeling that there’s something wrong with controlling the cat’s reaction via the method of water delivery. Her reaction is dependent on her state (wet), not the events leading up to it. One day you use a water pistol to get the cat wet, but you forgot to fire off the Cat class’ Action, and she’s perfectly fine with it.
There’s nothing perfect about either approach. You asked me about Fulcro: it is my opinion that event systems lead to chaotic design when used as the primary mechanism of decomposition, and OO programmers are very quick to use the same hammer for every nail, screw, bolt, hinge, airplane, and car they see. They have a proper place. You are right that there are some cases where there is something global that “happens” that a lot of actors might have interest in. Event systems can be a valid choice of design there. But don’t fool yourself. You’ve traded one problem for another. Now you have an event life cycle to manage. When do you register? What order are events delivered in? etc. etc. There is no “right way”. I’m pragmatic. In 3 years of building Fulcro apps I have never wanted events as a first-class mechansim (beyond the locality of state machines). But that doesn’t mean you don’t have a valid case for one. Please feel free to add it 🙂
Ah, one other thing: my target app “audience” is full-stack data-driven business apps. Micro-apps or graphics-centric apps, while easily doable in Fulcro, are not my target audience (and often benefit more from event-based systems). Fulcro has a definite slant to getting data in and out cleanly, and keeping the reasoning for that as simple as possible. I definitely do not claim it is the “right tool” for every job 🙂
If it’s any consolation, I haven’t yet seen an idiomatic way of doing this! And I’m certainly not claiming event systems are a panacea, and that I necessarily want to adopt them everywhere. The one that comes with the browser leads to enough complexity. I guess by happenstance I’ve stumbled across the cat scenario, where I have many different sources of mutation, and different types of mutation of a single thing, and yet I just want a single reaction to come out of it (in reality, there’s a diff on the old and new state, and a calculation as to whether it was a semantically meaningful change, and then the one thing happens or not). I’d say that most cases are not like this. Since it’s the single most complex component in my app (which very much is a traditional webapp for the rest of it), I’m trying out different ways of tackling it.
There’s a ton of things in Fulcro that seems appealing to me for the rest of the app. I’m querying a GraphQL endpoint on the server, so I’m suspecting that the remote system will play nicely with that, for example.
Right…and for targeted cases like that I think putting a watch on the state atom and looking for changes to a particular entity could be a quite valid approach. The reason it isn’t part of the core library is that IMO it shouldn’t be a common case, is easy to implement yourself, and providing it as part of core can lead people to overuse it for poor reasons instead of learning alternate ways that are likely to be a better fit.
To be clear, it’s frontend -> backend -> GraphQL endpoint somewhere else, so I’ll adapt between Pathom and it on the backend I think.
it’s much better at dynamically creating/collapsing graph edges, and allowing for contextual switching.
Fulcro is interesting in that it seems to take a much more out-of-the-box fullstack approach than other alternatives. This is something that comes up every once in a while, I’m sure you’re aware “why doesn’t Clojure have any frameworks?” Fulcro seems very much like framework as someone coming from language X would understand it.
But, where frameworks are a set of standard solutions to common problems, it seems geared towards it. I find this appealing, and will continue to learn.
Oh, I am painfully aware of our community’s distaste for frameworks….and in some ways I agree with them. And I did see Daniel’s post, yes.
pre-built devel server, cljs test-runner (before that had any decent story IMO), database adapters, etc.
I still believe it is quite necessary for wide adoption: you need something easy for the masses to get into. BUT, all that was too much for me to maintain, so a lot of it just wasn’t as good as something you could spin up yourself. I’ve settled on the approach that a template can contain some of that: A pre-built server, testing set up, builds already configured.
a forms library that autogens forms was what I was aiming for with the original forms ns, but it was just too much work with everything else I’m doing. Again, hoping the community will step up eventually.
I see how a lot of this stuff could be pretty easily done, but releasing it, documenting it, and maintaining it: not up for it myself. Not until there are 40 hours in a day.
I’ve probably had to abandon more code than I’ve kept in Fulcro, if you can believe that 😕
That sounds about right to me! There has to be an “enough is enough” somewhere as well. There are a set of things that everyone has to do: like talk to a server (except in the most toyish of toy examples), and then there’s the set of the things that are just common. I was surprised to see that there’s any kind of forms support in there at all.
FWIW, I think that Fulcro has bigger (or richer, you pick) set of idioms to understand than certainly Reagent at least.
two versions of forms…the first isn’t recommended…the latter just because the state mgmt is such a common thing to need to do, and normalized state makes it a trivial thing to attempt standardize. Reagent isn’t meant to be full stack, nor are most. I would expect Fulcro to be a bigger set than most just because of that diff.
Yeah, exactly, and this makes them more amenable to just pick up and run with. I mean, to me, this is not a problem, when the idioms are there to capture complexity that will arise in the end anyway. I can already tell that Fulcro aims to force you to deal with stuff up front that will otherwise build up and then swallow you. To a newcomer to the language, or to frontend development, I don’t think Fulcro will register as easy. I would tentatively put it down as an expert tool grown out of hard experience, and there’s nothing wrong with that.
That is the Achilles Heel. You can’t “bolt on” fixes. If you put the engine in the car upside down, you’re going to have to take the damn thing back out…bolting on an extra-long transmission and beating some dents in the left fender to make the alternator fit are, unfortunately, the norm in software 😜
but you are right: adoption happens because the thing looks approachable or even fun. RoR has that. You might involve 1,000 classes to get a damn string out of the database, but it’s “fun and easy” and there is a huge community around it.
Then you pay seven figures for your cloud deployment to make the damn thing run and go out of business (or if you’re in S.V., sell it for $1bn)
Yeah, but maybe you don’t have to be responsible for that part. If something is fun or not is kind of subjective. I mean, to some, “fun” might mean not dreading to touch a piece of code in case the card house comes falling down, and then being able to sleep at night.
But in reality I think it’s much simpler than that: “fun” is getting shit done. If a beginner can sit down and make it work, then it is fun.
Having scripts and examples that hand-hold you through it can have a big impact. It’s why I spend so much time on docs/videos
So far, I find that it requires a bit more attention and concentration than Reagent et. al. (because more comprehensive and more up-front), but moving Fulcro bits and pieces into my existing stack (Aleph rather than http-kit, Reitit rather than plain Ring) has proven surprisingly friction-free so far.
would love to see a community-accessible template with those as an example, if you ever feel like stripping out proprietary bits
I can do that, it’s hardly rocket surgery. The reason I used reitit is that it provides some nice symmetry between CLJ and CLJS, and I haven’t investigated yet how those come into play with Fulcro on the frontend. Given that it has its own idea about routing, it may turn out to be less important.
Well, it’s an SPA, so all back-end typically does with “routing” is send the same html file every time.
Yeah, originally is stuck the routes in a CLJC and provided different frontend and backend handlers in order to ensure the both types of navigation actually routed to the same place.