ioof-holdings / redux-subspace

Build decoupled, componentized Redux apps with a single global store
https://ioof-holdings.github.io/redux-subspace/
BSD 3-Clause "New" or "Revised" License
312 stars 33 forks source link

How to use with reselect? #68

Closed Unruly-Coder closed 6 years ago

Unruly-Coder commented 6 years ago

Could you provide an example how to use redux-subspace with reselect? I have a problem with react-redux-subspace and reselect. I use very complex selectors which based on root scope and have to base on a local scope as well.

jpeyper commented 6 years ago

Hi @Crazy-Ivan,

Its a bit hard to provide an example without understanding your use case a little better. To our knowledge that aren't any issues using reselect with react-redux-subspace.

Are you able to provide any code of your complex selector and how you used it with subspace, or provide a few more details on the problem you are seeing?

Have you considered using redux-subspace-wormhole to provide root state to your subspace?

Unruly-Coder commented 6 years ago

Hi @jpeyper,

redux-subspace-wormhole can be the answer. Base on documentation I see that I have to explicit map which part of the global state will be injected. Which breaks a bit open/close principle. I had to edit it every time I will need add selector which use part of the global scope, I didn't inject before.

const store = createStore(rootReducer, 
applyMiddleware(wormhole((state) => state.globalCarsCollection, 'globalCarsCollection')), 
applyMiddleware(wormhole((state) => state.globalColors, 'globalColors')), 
applyMiddleware(wormhole((state) => state.globalPrices, 'globalPrices')), 
)
export const getSomthingImportant = () => createSelector(
    scopedListOfIdsSelector, // [{carId, colorId, priceId},...]
    globalCarsCollectionSelector,
    globalColorsSelector,
    globalPricesSelector,
    (scopedListOfIds, globalCarsCollection, globalColors, globalPrices) => {
      return something
    }
);

In the example above globalCarsCollectionSelector, globalColorsSelector, globalPricesSelector are functions which expect rootState, scopedListOfIdsSelector expect scoped state. Injecting global scope into local scope is a trick which could work for reselect (if we won't have a naming collision). However, we have to be sure that we inject everything and be sure that there is no naming collision. Do you know how it could be solved?

Thank you in advance

mpeyper commented 6 years ago

@Crazy-Ivan, just so I can be sure I'm understanding correctly, you want to use reselect to create a selector to use in:

a) mapStateToProps param of a connect HOC used inside a subspace b) mapState param of a subspaced HOC c) mapState param of a SubspaceProvider d) other?

Base on documentation I see that I have to explicit map which part of the global state will be injected. Which breaks a bit open/close principle.

We do have a RFC open (#57) on how the wormhole api could be improved if you would like to add any thoughts you have on it (positive and/or negative).


Side note:

you don't need to use applyMiddleware so many times:

const store = createStore(rootReducer, applyMiddleware(
  wormhole((state) => state.globalCarsCollection, 'globalCarsCollection'), 
  wormhole((state) => state.globalColors, 'globalColors'), 
  wormhole((state) => state.globalPrices, 'globalPrices'), 
))

If you're really wanting access to all root values in all subspaces, you can just create a root (or whatever you want to call it) wormhole that has the entire state in it:

const store = createStore(rootReducer, applyMiddleware(wormhole((state) => state, 'root')))

The in your subspaces you can use state.root.globalCarsCollection, state.root.globalColors, state.root.globalPrices, etc. as needed.

Unruly-Coder commented 6 years ago

a) mapStateToProps param of a connect HOC used inside a subspace

mpeyper commented 6 years ago

At it's core, redux-subspace is about isolating the subspace's state and actions from the rest of store. Where ever possible, I would advise against relying on global values in subspaces. Having said that global state is often necessary to meet the requirements.

a) mapStateToProps param of a connect HOC used inside a subspace

In that case your only option is to inject the global values into the subspaces state. There are two official ways this can be achieved:

  1. wormholes: global values area available to all subspaces
    const store = createStore(rootReducer, applyMiddleware(
     wormhole((state) => state.globalCarsCollection, 'globalCarsCollection'), 
     wormhole((state) => state.globalColors, 'globalColors'), 
     wormhole((state) => state.globalPrices, 'globalPrices'), 
    ))
  2. rootState param: the second parameter of the mapState prop (for either SubspaceProvider or subspaced) will be the rootState, which can be used to merge into the subspaces state
    <SubspaceProvider mapState={(state, rootState) => ({ ...state.nodeForSubspace, globalCarsCollection: rootState.globalCarsCollection, globalColors: rootState.globalColors, globalPrices: rootState.globalPrices })}>
     <ComponentToSubspace />
    </SubspaceProvider>

Now the subspace can access the global values directly in their state, i.e. state.globalCarsCollection.

As mentioned before, if you don't want to explicitly name each prop, you could introduce a root node that contains the whole rootState. This could be done in either method.

const store = createStore(rootReducer, applyMiddleware(wormhole((state) => state, 'root')))
<SubspaceProvider mapState={(state, rootState) => ({ ...state.nodeForSubspace, root: rootState })}>
  <ComponentToSubspace />
</SubspaceProvider>

Now the subspace can access the global values by pathing through the root node in their state, i.e. state.root.globalCarsCollection.

Of course, you could write your own middleware to change the getState behaviour of subspace, but this is likely overkill for your use case.

Good luck, an please let us know if something here doesn't make sense, or if there is some way you think redux-subspace can be more helpful in meeting your requirements.

mpeyper commented 6 years ago

Oh, I forgot to mention about reselect...

Once the global values are in the subspace's state, you can use reselect like normal:

export const getSomthingImportant = () => createSelector(
    scopedListOfIdsSelector, // from the local state for the subspace, e.g. state.scopedListOfIds
    globalCarsCollectionSelector, // from state.globalCarsCollection or state.root.globalCarsCollection
    globalColorsSelector, // from state.globalColors or state.root.globalColors
    globalPricesSelector, // from state.globalPrices or state.root.globalPrices
    (scopedListOfIds, globalCarsCollection, globalColors, globalPrices) => {
      return something
    }
);
Unruly-Coder commented 6 years ago

Thank you, that is very helpful. One question. Is there any option to get the name of the subspace inside a wrapped component? ('react-redux-subspace')

mpeyper commented 6 years ago

One question. Is there any option to get the name of the subspace inside a wrapped component? ('react-redux-subspace')

Nothing that is officially supported.

Technically, if you have access to the store directly, then it will have a namespace field on it that will be the namespace of the current subspace.

this.context.store.namespace

Note: I am in not way endorsing direct access of the store from the context. Use at your own risk of the api changing.

I am curious what the use case is that requires the component to know the namespace though. If it's common enough, we could introduce a HOC into react-redux-subspace to inject it into a component.

mpeyper commented 6 years ago

Hi @Crazy-Ivan, is there anything left to do on this issue or can I close it?

Unruly-Coder commented 6 years ago

Use case: [Mobile app] I have several screens which make up one search functionality (search screen, specific filter screens, etc). Screens are connected together via navigations system in a layer above component definition. I had to use this search functionality in many places but the state should not be shared. To be able to navigate correctly between screens inside search functionality ( results screen -> main filter screen -> specific filter screen), I need to now on which namespace a user is.

mpeyper commented 6 years ago

Hi @Crazy-Ivan, just wondering how you are going with this?

I'm still not sure if there is anything actionable for this item. Happy to discuss further, or close if you feel there is nothing more to do.