Comments (9)
Excellent, an answer has emerged. @staltz I agree very much that "global" (application wide) is too wide in nearly all cases state. It's just the default for most systems following something like Elm or Redux, because it's a better fit than component local state bucket-brigaded up and down a complex component hierarchy.
Here is the answer I will use: at a component which introduces state to be shared by an arbitrarily deep tree of components underneath, use sources.onion.state$ and pass it downwards under a reasonably named entry in sources.
I think this pattern actually is still fractal; it can be used again, deeper down in a component hierarchy, for another subtree of components which need to share state.
from cycle-onionify.
@staltz How about including this more complex and realistic use case (an application which some state's component local and other is shared among a subtree of components) in the docs for onionify?
from cycle-onionify.
@kylecordes I've never tried, but you can already use onionify for global state. You can define two state channels by wrapping your root component twice in onionify:
const main = onionify(onionify(App), 'globalOnion');
Then the root component won't pass the globalOnion
source to its children, but instead only the state stream globalOnion.state$
:
function App(sources) {
let childSources = {...sources, potato: sources.globalOnion.state$};
delete childSources.globalOnion;
const fooSinks = isolate(Foo, 'foo')(childSources);
const barSinks = isolate(Bar, 'bar')(childSources);
return {
onion: xs.merge(fooSinks.onion, barSinks.onion),
globalOnion: xs.merge(fooSinks.potato, barSinks.potato)
// other sinks here
}
}
In this way all descendants will get the usual onion
source, plus a potato
source that is a stream of states that can't be isolated. (That's because isolate
relies on the sources to be IsolateableSource
s, i.e. objects with both isolateSource
and isolateSinks
functions).
I don't think we need anything more official. However there's the downside that potato
is not an onion source, so you can't access potato.asCollection
.
from cycle-onionify.
@kylecordes I'm trying to understand what are you looking for.
Do you want the whole application's state to be readable from any component? If so, you could achieve that by basically not isolating onion. You can do that by passing the identity lens as a scope whenever isolating:
const identityLens = {
get: outerState => outerState,
set: (outerState, innerState) => innerState,
}
const isolatedChild = isolate(child, {onion: identityLens})
Do you want to be able to manipulate the whole application's state in different ways? For instance, do you want to store the app state in localStorage? Do you want to debug it globally? That kind of stuff can already be done and one good example is https://github.com/maiermic/cycle-storageify
But I think what I really want to know is why are you looking for global state as a feature? Why is this something desirable? What problems are you trying to solve?
from cycle-onionify.
@abaco Thanks, that would work, though I hate to have the unused onionify features potentially interacting in unexpected ways.
@staltz I'm thinking of state that is not per-component, that does not follow the shape of the components. This is common in complex applications which have numerous components interacting around the same pool of state. This is handled well with Redux - actions and reducers that understand how one action taken by one place might affect the global state, then the rest of the system observes that global state to react on screen accordingly. I do the same thing heavily over in Angular with ngrx/store, the same idea as Redux but implemented on top of RxJS.
Of course I have in mind well-designed application-wide state, not a cesspool of global variables. The sort of thing one might do with various other tooling, like the stuff in Om Next or with ClojureScript+DataScript.
I'm trying to think of a published example application that has the complex state that crosses component boundaries widely. We build stuff like this every day at work. Here's a kind of application, very vaguely, that comes up for us repeatedly. An application which retrieves data from various endpoints, combines it, then presents it to the user in a complex set of screens involving visualizations, data tables, and so on. The user can then filter, explore and interact with the data, making proposed changes. The user can see their proposed changes application-wide, they are not local to the particular widget used to make them. The user can adjust filtering and they see the results of filtering application-wide. Very little of the state is per-component, per-widget. Yet the application is broken into many components/widgets to manage complexity. (Unfortunately I don't have any examples to share, all hidden behind customer firewalls and contracts. Mostly Angular in the last few years.)
from cycle-onionify.
[How to handle] state that does not follow the shape of the components.
When phrased like this, now I totally understand what you mean.
I recall building a Cycle.js app for a customer and I had somewhat this situation. I built it with onionify but at that time we didn't have lenses. And my workaround to get something like lenses was to build a "view model", basically a function that took state$
and added more fields and metadata, returning viewState$
.
Now I can see how I could have done that better with lenses.
Then again, I know that you want multiple components to manipulate the same "global" business data, it's pretty common. On the other hand, it's very likely each of those components will have some state, for instance some toggle boolean. And this is where lenses really shine, because you can get the business data from the parent, add the child's own state to it, but the parent won't see the child state. And you probably don't want to "pollute" the global state object with transient non-important component state fields.
And yet another observation is: even though the same business data may be used by multiple components, that doesn't mean you actually want the business data to be "global". If the business data is some financial charts, you want that data available only in the FinancialChartPage, which contains many children components. If you switch to the SettingsPage, you don't care anymore about financial business data.
So this is how I think usually absolutely global state is not a good solution. Typically "relatively global" is what they want. Relatively global is a silly name, since it's not anymore global. But the lesson here is that global is a very strong property. Too strong, it's not flexible, it's not negotiable, it's always global. I think nested scopes are usually the tool to use, and with onionify being fractal, they work really well. You can code the entire FinancialChartPage pretending that the state is global, but then you could embed FinancialChartPage inside MyApp, and all the state for FinancialChartPage would be scoped, so that SettingsPage wouldn't have access to that (unless the parent MyApp decides to pass some state from FinancialChartPage to SettingsPage).
from cycle-onionify.
I hate to have the unused onionify features potentially interacting in unexpected ways.
The only features attached to the onion source (StateSource
) are isolation and asCollection
- if you don't call them they won't interfere. Onionify has already been used elsewhere (e.g. cycle-color-picker) for the sole purpose of folding the stream of reducers into a stream of states. That's a perfectly legitimate use, even though it neglets isolation (the "onion" in "onionify").
By the way, calling onionify twice is not the only way to add a global state channel, you can also map onion.sources.state$
to select a substate:
const globalState$ = sources.onion.state$.map(s => s.global);
const fooSinks = isolate(Foo, 'foo')({...sources, potato: globalState$});
const fooReducer$ = fooSinks.potato.map(reducer =>
state => ({...state, global: reducer(state.global)})
);
@staltz Talking about global/local, I think it's okay to have some global state, especially if you keep two separate channels, one onionified and one global. After all, there's nothing anti-cyclic in global state, it's just anti-fractal.
from cycle-onionify.
I think it's okay to have some global state, especially if you keep two separate channels, one onionified and one global. After all, there's nothing anti-cyclic in global state, it's just anti-fractal.
It's definitely not anti-cyclic. By the way, I was going to say how to create two channels, one fractal the other not, but the approach is exactly what you showed: pass sources.onion.state$
downwards, and it won't ever be isolated because it doesn't have isolateSink/isolateSource attached to it.
from cycle-onionify.
Is it okay to say this issue is resolved? :)
from cycle-onionify.
Related Issues (20)
- pickCombine fails when re-adding item with same key HOT 1
- Possible to not emit until default reducer gets run? HOT 7
- Rename lens getter/setter HOT 8
- Help needed - MemoryStream.map not producing output HOT 1
- Add mock-onionify HOT 4
- Remove the .vscode folder
- Why don't provide 'pick' and 'mix' functions in xstream? HOT 4
- Shouldn't collections docs be in readme as well as release notes? HOT 2
- Add ES6 module build
- "Reducer" term is not correct HOT 1
- pickMerge throws error if child is not using sink HOT 1
- pickMerge seems to swallow events HOT 6
- type MakeScopesFn does not exist but imported HOT 1
- Shouldn't state emissions be microtask queued? HOT 6
- emitting `xs.never` with pickCombine might be wrong HOT 13
- Action stream is probably a better definition than `reducer` stream. HOT 4
- onionify typings assume "onion" as key HOT 1
- cycle-onionify when can update for rxjs@6
- cannot compile typescript examples HOT 1
- Define Omit<T, K> properly? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cycle-onionify.