Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Redux's relation to cursors #155
Redux seems to be similar in that everything is stored in one tree, but does not have the ability to listen to events on sub-trees.
I was wondering if you could provide some comment on Redux's approach, and the similarities and differences to these specific cursor-based apporaches. Is there anything possible with cursor-based approaches that's not possible with Redux?
I've used Morearty. My main pain point was that cursors are too low level.
Redux has the same idea.
WHAT to change:
HOW to change:
You can implement read-only cursors on top of Redux very easily. Just listen to the root changes, select a specific path and compare if the reference has changed since the last time.
In fact that's why Connector accepts a "select" prop. That's a function that lets you query a slice of the global state. Because it's a function, it is composable: you can make your own helper that lets you make nested "select"s just like you can nest read cursors. See NuclearJS's "getters" for an example of this approach.
What Redux does not give you is write cursors. This is a core design decision made for a reason.
Redux lets you manage your state using composition. Data never lives without a reducer ("store" in current docs) that manages that data. This way, if the data is wrong, it is always traceable who changed it. It is also always possible to trace which action changed the data.
With write cursors, you have no such guarantees. Many parts of code may reference the same path via cursor and update it if they want to.
This is similar to how React works. If you see a DOM node, you can trace which component owns it. If you remove the component, there will be no DOM node.
Redux provides similar guarantees for reducers and data. So indeed, it is "less powerful" than cursors in the same sense React's model is "less powerful" than jQuery DOM manipulation. But sometimes less power is actually a good thing.
@gaearon Do you see that as a philosophical/pattern stance or as a technical one? In other words, is Redux somehow preventing write cursors or otherwise causing the developer to fall into the pit of success?
For example, if I created a single action
@gaearon without cursors how do you solve data binding on text inputs?
In Atom-React I permit to use valueLink on cursors:
I find this handy, and it is actually my only real need for cursors, as in this kind of case they are much more convenient than firing actions/events for input keystrokes.
One could argue that I can use local component state for text inputs. That's true and I may do this in the future as my text inputs are becoming less responsive over time, but I think keeping everything in an immutable state is stilll a simpler model and try to keep it this way until the limits are reached.
Also I don't really understand the absolut need for listeners on cursors. If cursors are simply a path and a ref (like a lens with a ref), to the data structure, the only needed listener is the one on the root of the data structure, listening for swaps. Using listeners on cursor does not seem to be a requirement from me, at least if you always re-render from the very top (which may be less performant).
I can't really understand the need for read-only cursors. Why not directly pass the data if you don't need to write?
That's a great question! You can totally do that.
What I'm saying is that cursors are very low-level API. Just like you can have a single React component for your whole application, you can have a single
One important difference is that your
Just like in normal Flux. Subscribe to a state slice, fire actions on change. It's not really that much different from cursors. The difference is instead of directly causing change, you need to express it as an Action, so that it's possible to do cool things like go back in time, or undo actions from advanced devtools. But the result is the same: the state is changed.
I'd probably use local state but you're right it's worth to try pushing its boundaries. I know @chenglou has been experimenting with different ideas for React state. I'm potentially interested in an idea of “local” stores defined by components and attached/detached from the root Redux instances while the app is running, while still living in the single state tree and thus having the benefits of Redux: action replay, logging, etc. So I'm with you here and it's something I'd like to explore later on.
We don't always re-render from the top for performance reasons. Also re-rendering from the top only works if you always pass the props down explicitly, which we don't do in Redux. We're using
Yeah exactly. That's why I'm saying you don't really need cursors with Redux. The only use case for “read-only” cursors is nesting (component A receives some path and gives component B some subpath without thinking about the current path) but it's solved by composing
referenced this issue
Jun 21, 2015
Actually when calling
This may seem a bit hackish as it is soomehow "internal framework actions", but what you want to do to mount locat state in the data structure seems to be almost the same kind of stuff.
Anyway I guess my usecase of form inputs will be solved with what you are working on, as I could then bind the input value to the local state instead of a cursor :)
But in my experience after working with them for a while, this created some confusion for our developers to not really understand when to use or not a cursor. So they use them a bit everywhere while simply passing the data would be largely enough. We end up with some very simple components that display a single line of text, taking a cursor as a prop :(
I agree with the performance need for listeners, as I start to see the limits of always re-rendering from the top: I need to be more and more careful about performance of some components, like the top-level layout components, that are rendered everytime.
referenced this issue
Jul 15, 2015
Take this in context of me looking for inaccuracies in my understanding of Redux and related concepts:
If I understand Redux correctly (just starting to look at it, so making lots of assumptions here) there seems to be a very subjective and almost artificial distinction between cursors and read-only subscriptions to subtrees combined with action dispatch that results in state change. Cursors can be functions or even methods on cursor classes that take the select argument in the constructor and return a cursor to the subtree with some custom behavior like what @slorber said (fire set actions etc) ... and they can have in and out transducers or whatever you want. The name cursor would normally mean ability to traverse a structure much like a cursor in say IndexedDB (moving up and down the rows) or even a keyboard cursor (moving up, down, left and right thru an ordered tree) but the way "cursors" show up in popular culture a la Om and other projects is akin to the concept of "lenses" ... or at least that is my own understanding.
The interesting thing is the behavior of the action dispatch, whether it is sync or async. In similar architectures to Redux, we've seen a problem with controlled components like text input when using async and typing fast and seemingly losing characters (due to delayed state change relative to actual input and the reaction to a delayed state transition) I assume the dispatch method in Redux is sync not async.
I would appreciate it if you could weed out all the fallacies in my take so far... or clarify things for me. Thank you. Redux is good work @gaearon
All I'm saying is that you wouldn't use such abstractions to write in Redux. You can use cursors or lenses or selectors or whatever to read fine, yep.
Yes, it's synchronous unless you're using a middleware to make it asynchronous on purpose. Please see the source.