Giter Club home page Giter Club logo

Comments (19)

conartist6 avatar conartist6 commented on June 21, 2024 1

I'm no longer actively considering this, so I'm going to close it for now.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

OK I figured out that I should do something much simpler than I previously had to handle subspaces with thunking. And I can just set subspaceOptions at the top level if all my subspaces need the same middleware. But if any subspaces needed different middleware they'd currently be unable to configure it.

from redux-subspace.

mpeyper avatar mpeyper commented on June 21, 2024

The subspaceOptions is an internal API we use to transfer information from parent subspaces when creating child subspaces (e.g. what middleware was used when creating the root subspace).

The ability to have different middleware applied to different subspaces is not something we've supported (yet), outside of marking them to only be applied at different levels.

from redux-subspace.

mpeyper avatar mpeyper commented on June 21, 2024

There were plans to allow something like const subspacedStore = subspace(state => ..., 'namespace', applyMiddleware(someMiddleware)) but due to the complexity of how this would merge with middleware provided by the root and the variety of wrappers that we provide the create a subspace internally, we never prioritsed it over other work.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

I've been doing a hack using subspaceOptions to pass the right enhancer at the subspace level and it seems to work fine. I don't want the central store to need to know about subspacing at all. Aside from this strageness with middleware it doesn't, which is great because I don't own the central store setup in the codebase I work in.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

If you have more information about the requirements/cases to test for a feature like that I might be interested in picking up the work myself.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

Really though, isn't the complexity of how it merges with the root already handled?

from redux-subspace.

mpeyper avatar mpeyper commented on June 21, 2024

Well, consider the following setup (assuming the api existed):

const store = createStore(reducer, applyMiddleware(middleware1))

const parentSubspace = subspace(state => state.parent, 'parent', applyMiddleware(middleware2))(store)

const childSubspace = subspace(state => state.child, 'child', applyMiddleware(middleware3))

store clearly has a single middleware (middleware1). What about parentSubspace? Does it have:

  1. a single middleware (middleware2)?
  2. 2 middleware (middleware1 and middleware2)?

And what about childSubspace? Does it have:

  1. a single middleware (middleware3)?
  2. 2 middleware (middleware1 and middleware3)?
  3. 3 middlware (middleware1, middleware2 and middleware3)?

There are 2 ways I can see it working:

  1. provided subspace options override the inherited options
  2. provided subspace options merge with the inherited options

With option 1, the you run the risk of having some global functionality that the root store wants (e.g. we use middleware to provide analytics on certain actions), but conversely option 2 runs the risk that the parent's options clober the child's behaviour in some unintended way. There is also a concern that some thing just work by accident (e.g. thunks are likely to be included at the root level and so the child never needs to worry about adding it) which can make some things feel a bit magic to newer users.

My gut says option 2 is the lesser of 2 evils here and we can work around it easier using the middleware scoping helpers and the order we apply the enhancers (although theoretically there may be other subspaceOptions in the future, but for now there is only 1).

It could be as simple as a wrapper that does something like:

const subspaceWithOptions = (mapState, namespace, enhancer = (store) => store) => {
  const subspaceEnhancer = subspace(mapState, namespace)
  return (store) => {
    const subspaceOptions = { enhancer: compose(store.subspaceOptions.enhancer, enhancer) } // I can never remember the order to do these, but we want `enhancer` to be called before `store.subspaceOptions.enhancer` to ensure the child middleware get the first shot at the actions
    return subspaceEnhancer({ ...store, subspaceOptions })
  }
}

Ideally, this would just be the default signature for the subspace function but the way we default and resolve the parameters might make having a more elegant solution overly complex 🤷‍♂

Then we just need to work out what this looks like for the SubspaceProvider and subspaced component in react-redux-subspace, subspaced in redux-subspace-saga and subspaced in redux-subspace-observable. For sagas and observables this is likely to just be a third parameter to their signatures. For subspaced in react-redux-subspace this should also work, and an additional enhancer prop for SubspaceProvider seems appropriate.

Alternatively, we could pass an options object and instead of passing the enhancer (in case we want to add more options in the future. That said, it's been years now and no new options have shown up, so perhaps we are better off with the simpler API for the moment and just make the breaking change if we ever get a new one?

I'm happy to see this worked on if you want to take a run at it. There is a major version coming (redux v6/7 update) so don't be too concerned about backwards compatibility, but having it is always nice.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

Here are my answers to the questions you pose:
parentSubspace has one middleware of its own. That is to say one middleware execute on the parentSubspace store. The remaining middleware still gets a chance to handle the actions obviously, once they have been namespaced and state is the root state.

childSubspace is basically the same, assuming you mean it to derive from parentSubspace. It has one middleware of its own, one that could handle its actions as they look to parentSubspace, and one that can handle its actions as they look to the root store.

This is to say, there is no inheritance (no merging or overriding), only composition. This fits with the general sandboxing paradigm, as well as my experience trying to get redux-thunk to work. It was confusing as all hell until I figured out how to think about it. A subspace store owns every aspect of the actions it creates, right up until the boundary with its parent. If the subspace wishes to be able to thunk, it is the subspace’s responsibility to include that middleware, not the parent. That is because only subspace has the store that thunk needs to work on.

I’d say that what this implies is that redux-subspace should prohibit (configurably, perhaps) alternative action forms at subspace boundaries. That is to say that thunks/sagas (functions), Promises, etc. would all trigger errors if they appeared as actions at the point of passing a subspace boundary. This makes very clear that it is the child’s responsibility to set up its redux environment the way that it needs to be set up, and should address your concerns about anything being magic.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

I believe this would allow you to deprecate some of the more complex existing functions like applyToRoot, applyToNamespaceRoots, and applyToChildren.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

Would it be possible then for a subspace definition to actually be a store?

const childStore = createStore();

const subspacedChildStore = subspace(state => state.subState, “SUB”)(parentStore, childStore);

At that point redux itself would complain about undispatchable action types that reached the store level. Instead of adding enhancers, you’d just add a childStore.subscribe that did the namespacing and redispatched to the parent. Parent’s getState would just call the subspace’s getState.

I feel like at the point I’m talking about a completely different solution to the same problem, but it is intriguing. Have you thought about a solution like this previously?

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

I don’t like that example actually. The child store should be untouched. The API would be more like:

const childStore = createStore();
mountSubstore(childStore, state => state.subState, “SUB”)(parentStore);

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

At that point you also don’t need a <SubspaceProvider /> component. You could just use the react-redux <Provider />.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

I think I’ll try writing this up as an experiment. The only real way to see if this the pattern has problems is to try it and see if it works.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

Yeah OK that would have problems. The biggest one is that all the subspace reducers become invisible. Being able to enhance subspace reducers is core to what makes redux-subspace so powerful.

from redux-subspace.

jpeyper avatar jpeyper commented on June 21, 2024

I don't want to derail this issue, but...

@mpeyper and I have pondered about whether subspace is going about things the wrong way, and instead having lots of individual stores, with a "meta store" that handles communication between the stores when it is desired (ie. wormholes or global actions) might be a better way to create isolation with less gotchas (the child stores will have full control over everything and only share what they want).

That idea sounds a lot like what you were proposing. We've never really explored the idea beyond a "what if" conversation.

Seems you've given it a bit of a thought as well and I would be interested in understanding you're thoughts on it, and what the potential downsides and difficulties might be.

But in regards to this ticket, is there anymore you'd like to discuss or do here? Or should it be closed off?

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

Keep the ticket if you don’t mind, I’m pretty sure I still need a way to do this. I’m going to keep thinking about it.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

I've thought about it a little more. I think it boils down to this:

A subspace store should be created once and only once, after which it should be possible to look it up the subspace store instance from the root (or parent) store. The API for this might look something like store.getSubstoreByNamespace('a'). Without this bit every React wrapper would have to figure out how to apply the middleware independently. As a bonus the existing React API could be simplified to something like <SubspaceProvider namespace="a">...</SubspaceProvider>.

In order to do support getSubstoreByNamespace, add a new method, something like registerSubspace(namespace, subspaceStore)(store). This method would take a subspace store in order to give the consumer an opportunity to apply enhancers to the subspace store.

from redux-subspace.

conartist6 avatar conartist6 commented on June 21, 2024

The user would remain responsible for attaching the subspace reducer to the root reducer, and for using subspace() to create the subspace store to be enhanced.

Potentially the existing createSubspace function could both create and register a store so that existing code patterns would not be broken. Then perhaps a method like createUnregisteredSubspace would be used in the more advanced cases where we want to create, enhance, then register.

from redux-subspace.

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.