Fork me on GitHub

@johanatan as president of the Cursor Disapproval Society I'll simply shake my head slowly 🙂


@mikethompson: what's your problem with them? It's a lot less code this way and my app is not so complicated as to need derived events


Cursors will be fine if your app is fairly trivial. But they don't scale.


So, sure, if you think your app is simple, and likely to stay that way, nothing wrong with Cursors.


But as your app gets more complex, they'll bite you


As your app starts to get a bit more complex, you'll notice that cursors encourage you to put logic into the wrong places.


This is a principle that has been repeatedly re-discovered, time after time in different contexts. Anyone who doesn't know the principle will get bitten and have to rediscover it all over again. I'm so old, that I once programmed in Eiffel. We knew it back them The modern advice looks like this:


I remember seeing "cursors" in OM, when it first came out (to rapturous reviews) shaking my head and thinking we're not STILL re-discovering why that approach is a bad idea are we?


Sure enough OM Next doesn't have cursors (it now has separate query and commands)


Anyway ... if your app is simple enough then you'll absolutely be able to get away with Cursors. (Because you app is simple enough that you can get away with anything). But as app complexity incresased bad things will happen to your architecture


@johanatan: With no opinion on cursors but as a fan of re-frame, I'm curious what parts of your app are so simple that you feel like re-frame is too much overhead. Do you have any examples you could share so I can see what kind of things you've found cursors to be well suited for?


Cursors are useful when you want a reference to a sub-tree of your app-db. If you eschew them, then you flatten your tree and everything becomes top-level and needs to have a subscription and a handler defined for it.


But many things are really trivial and do not deserve that sort of promotion.


Just as you may have a local atom somewhere which is not tracked at all in app-db, you may instead put the would-be local atom deep inside app-db and refer to it with a cursor.


i.e., (It is tedious to create endless subscriptions and handlers for the trivialities).


And the justification for the use of local atoms for some data should equally apply to the use of cursors for those (or other) similar data.


I've also found it quite nice to be able to re-use components for different purposes in the app and passing in only a prefix for where in app-db to root their data. Within a component everything is relative to the prefix supplied and thus it doesn't really matter if those atoms are local in deed or in spirit.


I've found that as I worked more in Clojure, my desire for deep trees or heavily nested data structures pretty much disappeared - it seemed to bring me more headaches than it was worth. In terms of my re-frame work, I've found that any time I needed access to something a bit deeper I could get by happily with either passing in a higher level subscription or storing the data in a local atom as part of a basic reagent component. In every case where those didn't work it was worth adding in a new subscription.


Isn't app-db by definition a deep or heavily nested data structure?


It certainly hasn't caused me any problems treating it as such (especially with the prefix passing idea I mentioned above).


And I think the presence of assoc-in in the standard library kind of validates the approach.


For the components where you pass in a path, is that essentially the same as passing in arguments to a function? Like passing pointers around in C style languages (if you've had that fun experience)?


No, it's a vector of keywords


Just like the argument that assoc-in accepts


But yes it is just another input to the component


(In some cases the only input, in others along with other parameters)


Cursors themselves can also represent the prefix: you can use either an atom or a cursor as the base point for another cursor (and in some cases I do this rather than pass the vector prefix directly).


Yeah, but once you traverse that path through the vector, what’s at the end of it? Would it be possible to create a component that takes that data instead of the path, and in the calling function you traverse the path then pass the data to the component instead? Or is there a particular reason that would be more difficult?


The path is the root of the sub tree where the component is to read and write its data. I have no idea what you're suggesting but if you have a 'text-field' for example it needs to read and write its data in an atom-- not merely "receive data" as you suggest.


And on the app-db side, it can be as heavily nested or as deep as you want - I try and keep it as flat as I can now, where my initial tendency was to limit the initial surface area and go deep first.


My progression was the exact opposite: I started out with everything flattened and top level and when I grew tired of that tedium (and the handlers and subscriptions that it entails) then I transitioned to a deeper tree w/ cursors (and now use them almost exclusively).


And I have experienced no bad consequences from that pattern.


Interesting! One of the things I absolutely love about Clojure has been the freedom of experimenting and adjusting without huge frameworks holding me back.


Ya it's pretty easy to adjust these things later on as things need to be refactored


And, btw, the use of cursors does not imply a violation of CQRS: the "reading" of my data (i.e., the interpretation thereof [the assignment of semantics to it] is definitely a segmented process from the raw capture of it [which is the part that is facilitated by the cursors]).


So in this model the UI is already hierarchical-- why not store the data representing the state of the UI with the exact same hierarchy (i.e., a raw-ish form) and then interpret that data elsewhere? This still seems to be in line with the spirit of CQRS to me.


On the example of the text field, for the stuff I usually work on the text field isn’t the component that controls the logic, it’s whatever container holds the text field. The container passes in the initial value and sets up whatever handler needs to be called (on keypress, blur or submit). All the local state and values are either held inside the text-field component or in a generic :editing key in app-db - the user can only edit one text field at a time so it’s worked well so far for me to have a kind of global key to hold that interim data (when it had to be held in app-db, that is). This might be driven by the fact that I mainly write business apps that have very tight ties to databases, so there are DB queries going on pretty regularly.


My point regarding the text-field is that it needs to receive an atom-- not merely some "data", as it needs to both read and write data.


(In response to your question about why I'm not just reading the data sitting at a prefix and passing it down).


Ahh, for me I’ve got the reading data passed in as an argument, and the writing is calling a function and passing it some arguments. So the component really doesn’t care about where it’s data comes from or goes to for the most part.


Right, you're doing: re-frame/dispatch in your on-change yea?


But in your handler for that, you probably have a single line of code that sets an atom.


Yeah, I think so (code isn’t handy right now so relying on faulty memory)


Hi. So i am generating animated SVG with reagent ( Is it possible to allow the user to download the image (as svg)?


As a first step, you can use render-to-string to obtain the SVG you are generating: