Giter VIP home page Giter VIP logo

Comments (9)

kylecordes avatar kylecordes commented on June 8, 2024 1

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.

kylecordes avatar kylecordes commented on June 8, 2024 1

@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.

abaco avatar abaco commented on June 8, 2024

@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 IsolateableSources, 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.

staltz avatar staltz commented on June 8, 2024

@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.

kylecordes avatar kylecordes commented on June 8, 2024

@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.

staltz avatar staltz commented on June 8, 2024

[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.

abaco avatar abaco commented on June 8, 2024

@kylecordes

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.

staltz avatar staltz commented on June 8, 2024

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.

staltz avatar staltz commented on June 8, 2024

Is it okay to say this issue is resolved? :)

from cycle-onionify.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.