-
-
Notifications
You must be signed in to change notification settings - Fork 419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Global sources & sinks #191
Comments
What’s wrong with returning There’s another pattern you can use, and that is to inject components into other components. This way, you can instantiate a component on an upper level, where you then have access to its |
where would you're single point of truth live in this pattern? just sounds like Having that single point which you can refer to anywhere in the app is a really useful tool, when you start dealing with larger apps. |
Aren’t component state determined by the properties they receive? How then can you have a single point of truth unless you gather it from multiple sources? |
This is what I'm proposing a way to channel state into a global state object. const sources = {
State: {store} // store would be where all global state lives
DOM: makeDOMDriver('#application'),
History: makeHistoryDriver({
hash: false,
queries: true,
}),
};
run(mainApp,sources); The only problem that I can't work out is in children component you would need the ability to update or do you think that it would have no value and you'd be quite happy without it? |
I know this isn't going to be liked but the way React has props which automatically bubbles down a tree is really useful. In Cycle you would have to manually pass down that information from parent to child. Redux took advantage of that channel and used it to pipe |
You are proposing a storage driver of some sort. I have no problem with that. Actually, we already have a LocalStorage driver. |
cool, just to expand a little on the idea of bubbling $state down. The black lines are how state would travel around the app via a This next image is if Now the bit that I can't work out is you would still have to manually pass Do people think there would be a way for this information to be automatically passed down via it's responses? maybe a custom wrapper for all components that has this built in?!? Or is that just adding/imposing too much. Cycle is really clean at the core but it feels like a sore point to be having to manually make sure the With React I know that if a Is this just a bad idea? |
Personally, I don’t like the magic that you are proposing. I think it defeats the purpose of reasoning about data flow. How components are composed should be entirely up to the developer. What state of a child is relevant for a parent should also be entirely up to the developer. How the data flows should be entirely up to the developer. But feel free to bypass all that and implement your app as you like. However, I wouldn’t recommend such an app as a good starting point for new Cycle.js developers. |
Your data flow is going to be one massive mess though, if you're implying that everything should be manually piped. That idea is fine when you're dealing with something a couple of levels deep but going by my pictures say I now want page This is why I feel a single point of truth really comes in handy when dealing with a lot of shared state through multiple levels of parent => child relationships. because if you're wanted these Yeah you have a good reasoning about your app's data flow but lets now add another 3 |
@Cmdv this is just the difference between top-down approach and bottom-up approach. If you want global shared state like in Flux and Redux, then you really actually just want one dataflow component, because the Cycle.js equivalent of props-only View in Flux is just functions Single global state object is the top-down approach where you start with the assumption that all state is in that global object, and all the views are just derivations of that. Stateful components is the bottom-up approach where you start with existing components and you put them together by composing their behaviors. In this approach, manual "piping" as you call is unavoidable. To some extent in my opinion, I like manual piping because I don't believe it is as tedious as you describe, but it does give you an explicit picture of what happens, without magic underneath. That said, there are tradeoffs. Neither top-down nor bottom-up is a clear winner and it's not fair to try naming a winner.
So, yes, with Nested Dataflow Components you need to manually pass sources and sinks from parent to child and vice-versa. But its a cost we pay in order to get better reusability and explicit contracts on the component boundaries. If you really really want Redux or a single state atom approach, go ahead for it, but I don't think we can do a compromise between Nested Dataflow Components and single state atom because the two are opposed architectures. |
I don’t agree.
Not really. It’s just passing properties. |
"If you really really want Redux or a single state atom approach, go ahead for it, but I don't think we can do a compromise between Nested Dataflow Components and single state atom because the two are opposed architectures." This needs to be explained a hell of a lot more. There was the Flux Challenge as well, what was learnt from that exercise? I agree that for complex apps a one atom state is useful. The whole problem here is that people are shooting off their ideals while only working on super small concept apps. I propose that if your going to be so dogmatic at least provide a A vs. B app example, where one is done top-down and the other bottom-up. I'm sure @Cmdv and others can come up with the challenge, it would be interesting to then see the expert response from both sides of the fence. |
@staltz I didn't realise what I'm trying to achieve is a top down approach. Could this specific data handling be a function/driver? much like the This would still result in piping but at least you wouldn't have to handle the parent => child part if you were a few levels deep. The only reason I wanted to feed it to the But if I keep that functionality inside a function/driver. I think that I then wouldn't be moving away from the FP paradigm and it could just be seen as a glue function do you think this is a better approach? |
One is top-down, the other is bottom-up. What would be a compromise between those? Middle-center? That's why I don't think it's necessary to explain, it's pretty obvious top-down and bottom-up are opposites.
I wrote a blog post about the ideas, and you can check Flux Challenge if you want.
You can put "stuff" in drivers, but that's not the point of drivers. As Nick Johnstone put it, "Drivers encapsulate imperative side effects". That's all there is to it. State is not an imperative side effect in my opinion. Also, because the single state atom approach has state as the main piece of the application, you're not supposed to put main business logic in drivers. Drivers should be "plugins" to use imperative side effects, nothing else. I know where you are trying to get, which is minimizing code for manual "piping". Drivers are not the way to achieve that, neither Nested components are. The way of achieving that is building an architecture that has only one MVI. It has the top-level M, the top-level V, and the top-level I. Then, "dumb components" are just simple pure functions from state to vtree. Model is the place where to keep state, even the global shared state in the case of the top-down approach. If you find manual "piping" bad, please show us those parts of your code which you don't like, maybe we can find a way to refactor them. But I'm quite sure the explictness of the piping will remain because it's inherent to bottom-up approach. And there isn't a middle ground between bottom-up and top-down. Not as far as I know... |
"I know where you are trying to get, which is minimizing code for manual "piping". Drivers are not the way to achieve that, neither Nested components are." Hopefully there would be a better reason than that to require a one atom state! Architecture is also driven by plain old user/app functionality, the toolkits must bend to the rules of the user/app and so what the user wants in the end determines what the software is expected to do. If a given framework or API is to crippled to handle the demands then so be it, just never lose sight of what the user expects and needs because they are always right and the ones who will pay you for their good pleasure. Is having a bottom-up preference not also at least a significant reason why there is a sudden interest in Most.js over RxJS? It seems to have been discovered/acknowledged that RxJS is to slow for all the piping required to enforce bottom-up dataflow ideals. |
A toolkit is composed of several tools, hopefully. The composition of the kit is entirely up the user. However, if the user wants a hammer to drill, the user should get a drill in the kit. Demanding that the hammer ought to do the job of a drill is just ridiculous.
Absolutely not. While Most.js is extremely fast, RxJS is by no means "too slow". |
@Frikki I think you misunderstood my intention. I was just saying that frameworks and APIs are really only useful if they serve a purpose. In my creative world that purpose comes from imaginative and aspiring users. There is hence sometimes a gap between their vision and what present age software can actually do, any compromise would just not work or sell out the purpose. Agreed, a craftsman needs more than one tool in their toolkit, also good engineering skills can't hurt in the tight spots. In the end a muscle builder (framework) can take all the steroids (Most.js, etc) they like and look like the hulk. Yet, if they can not jump into action and save the lady or the day what is the point? There are apps to build and experiences to improve upon, hopefully we'll start to see heroic actions (apps) that were made with Cycle soon enough. That is if we can keep from tripping over our own top-down and bottom-up wiring. |
@HighOnDrive We have already seen an app, power-ui, built with Cycle.js, and it uses the bottom-up approach. Whether it saved the lady of the day, we’ll have to ask @staltz and Futurice. Again, which approach is chosen, top-down or bottom-up, is up to the developers; both are entirely possible. Staltz has already outlined how to achieve top-down wiring above. |
@Frikki Absolutely, we have a few scattered examples to dig through. When the new Cycle update happens what seems reasonable is to have some app templates produced. Just wireframe style templates that have no real contents but they wire up generic and chained drivers, routing and top-down or bottom-up scenarios. These do not have to be app starters, they would just clearly demonstrate the dataflow options. |
@staltz Thanks for the in depth explanations. The only reason this came up is because I'm trying to imagine cycle replacing apps I work on a daily basis. For example we have a Diagnostic flow with multiple routes through the application with multiple endings. (a total of about 30 pages) As you move through the app you pic up various pieces of information/data required.
There are more but as a quick example, some pages need to use some data whilst others don't. All pages need to be aware of 1. You need to be able to keep the a list of the pages visited etc.... (sorry you get the point though) So before this dataflow was a hard one to reason about when say a new implementation needed to be put in. As a new dev to the team it took me a while to start to understand how everything was talking to each other. (still don't fully understand). Then we decided to use the idea of what Redux implements and having this global Object where we knew was the singular place to check for the current state of things and change those as and when need be. If you build a new page or implementation you know that what ever you are going to interact with is going to be orientated around this Global Object. I know this is not what everyone would want from an app, but I hope it can it can be seen that dealing with data in this way still allows you start to build and expand as you wish. But saying that I understand that this is seen as a top-down approach now and I'm going to go away and research some more experimenting because I don't think this is a straight forward black and white thing. There are many languages that be seen as better than JS but the reason JS still does so well is because of the simplicity of using the language as a new comer. I know thats fully off topic but simplicity and easy to reason about are always good design choices I'd like to think :) |
Ok I've been doing more digging and I can totally see what @staltz + @Frikki were trying to show me. I came across this great article from Erik Meijer:
I'll close the issue down now, thanks for the great insight 😺 |
To be fair, if you have cases where many components need access to a common state, you can build those many as a single MVI, but still be able to use this MVI component in an even larger application. You know what I mean? No matter what internal structure your Cycle.js app uses, you can always embed it inside a larger Cycle.js app. |
Thanks @Cmdv for that great link! As I'm learning to do more with monad pipelines the need for a central global data store is shrinking. I'm happy with a single top-down MVI DFC approach yet would like to decorate my app tree with many Christmas lights/widgets. Seeing each observable like redux store with a reduce pipeline that can be as fancy as required, goes a long way. Being able to subscribe to any observable junction in a pipeline extends the options yet further. I just think it's very important to produce real apps. The statement made in the comments of the linked article (quoted below) is a reminder that technical or academic excellence alone is not enough. If it was not for the real app I'm building I would surely lose my way, glad then that it serves as my north pole. "Haskell was first made available in 1992, over 23 years ago, and according to Wikipeda, over 5,400 libraries and tools are available for Haskell programming. What language would be called a success that has more than 5,000 libraries of functions and not a single major program that was completely created in it? The only answer I know of is Haskell that was made and promoted by a group of publicly paid professors with next to no "real world experience" between them." Can you tell that I am not one bit nostalgic or in need of baggage, my own app already tells me what is right and how I must proceed. |
@staltz In the list of comparisons made between TopDown vs BottomUp, I think that one can have peer communication in a much easier way (or may be the only way) in TopDown approach. For instance, if I have a component |
@tusharmath You mean the proxy Subject technique? |
@staltz Yes, referring to this discussion — #170 |
@staltz We could infact have an immutable datastore driver, which would maintain all shared states at one place? |
Bottom-up approach simply means you have built the child component first, and it manages its own state because you build it as if it would be the entire application. And only after that have you decided to compose it in the context of a parent. So Bottom-up doesn't mean that you can't compose children components. If the parent needs to maintain some common state for all its children, it's still a Bottom-up approach as long as you didn't start building the parent. If the children cannot live (be reused) in other applications, then it's not bottom-up. |
How do you pass sinks back up a nested tree without piping it through each branch?
eg: 3 pages, on one of the pages there is intent to +1 / -1 to a number. I want all other pages to be able to listen to that change and update their own number. if I start with 0 click + 1 and move around the app all pages have a number 1.
correct me if I'm wrong but it feels like that sort of state is only kept within each component? so your sources at the
Cycle.run
level don't have a clue about whats going on elsewhere unless you passed it along each level until it reaches the top. Not so bad one level deep but what about 3-4 levels deep!redux is a little noisy but it is passing everything back to the top level and feeding it down when a change happens, then the whole tree has the ability to listen to that change (in React via it's props)
Here is a link get a visual representation of what I'm trying to achieve.
The text was updated successfully, but these errors were encountered: