RFC: Components sharing state between subspaces
mpeyper opened this issue · 4 comments
We recently had a requirement come up where two related components needed to share state with each other. Both components came from the same micro-frontend package and shared the same reducer and actions. The problem was that one of the components was nested inside another subspace. The component hierarchy was something like:
- app
- summary
- component1
- component2
- summary
redux-subspace is quite good (IMHO) at encapsulating the the summary
state from the component2
state, so consequently the resulting state structure was:
- appState
- summaryState
- componentState
- componentState
- summaryState
with the store being created like:
const summaryReducer = combineReducers({
// other reducers
componentState: namespaced('component')(componentReducer)
})
const reducer = combineReducers({
summary: namespaced('summary')(summaryReducer),
// other reducers
componentState: namespaced('component')(componentReducer)
})
const store = createStore(reducer /*, ... */)
Note: component1
and component2
are expecting to share the state in the same reducer, which was not occurring.
The hack solution we used to get around this was to wrap component2
in a subspace to fake out the summary
layer. The simplified version looked something like:
<App>
<SubspaceProvider mapState={state => state.summaryState} namespace="summary">
<Summary>
{/* lots of other stuff */}
<SubspaceProvider mapState={state => state.componentState} namespace="component">
<Component1 />
</SubspaceProvider>
</Summary>
</SubspaceProvider>
{/* lots of other stuff */}
<SubspaceProvider mapState={state => state.summaryState} namespace="summary">
<SubspaceProvider mapState={state => state.componentState} namespace="component">
<Component2 />
</SubspaceProvider>
</SubspaceProvider>
</App>
and the store is created as:
const summaryReducer = combineReducers({
// other reducers
componentState: namespaced('component')(componentReducer)
})
const reducer = combineReducers({
summary: namespaced('summary')(summaryReducer),
// other reducers
})
const store = createStore(reducer /*, ... */)
This worked and we were able to move on, but it bothers me that we had to fake a layer of the state to allow the components to work as intended. We were also lucky that we were actually able to push component2
's state down a layer to make it work. I can quite easily see a situation where both components are in entirely different levels in the tree, e.g.
- app
- header
- component1
- summary
- component2
- header
Our solution could not work in this scenario.
As soon as this occurred, @jpeyper and I started spit-balling how redux-subspace could help out in scenarios like this and we came up with this.
The idea is that you could flag a subspace as transparent
which will behave as a normal subspace for any component mounted and action dispatched within it, but if a subspace created from it it will use it's parent's state and namespace instead of it's own, as if it doesn't exist to the nested subspaces.
Effectively, it's just following React's advice for these types of scenarios and allowing the common state to be lifted up to to the common ancestor.
Going back to the above example, it would change to:
<App>
<SubspaceProvider mapState={state => state.summaryState} namespace="summary" transparent={true}>
<Summary>
{/* lots of other stuff */}
<SubspaceProvider mapState={state => state.componentState} namespace="component">
<Component1 />
</SubspaceProvider>
</Summary>
</SubspaceProvider>
{/* lots of other stuff */}
<SubspaceProvider mapState={state => state.componentState} namespace="component">
<Component2 />
</SubspaceProvider>
</App>
and the store could now be created as:
const summaryReducer = combineReducers({
// other reducers
})
const reducer = combineReducers({
summary: namespaced('summary')(summaryReducer),
// other reducers
componentState: namespaced('component')(componentReducer)
})
const store = createStore(reducer /*, ... */)
I did a quick proof of concept locally and it can be done, so now I have some questions:
-
Do we want this?
-
Is
transparent
the correct term for this?
It came about because a child subspace would look straight it to it's parent when resolving state and namespaces -
Should it be all or nothing?
Under the current proposal,transparent={true}
would result in all nested subspaces ignoring the it. Instead, we could use an array of namespaces (regex?) to give more control, e.g.:<App> <SubspaceProvider mapState={state => state.summaryState} namespace="summary" transparentTo={['component' /*, ... */]}> <Summary> {/* lots of other stuff */} <SubspaceProvider mapState={state => state.componentState} namespace="component"> <Component1 /> </SubspaceProvider> </Summary> </SubspaceProvider> {/* lots of other stuff */} <SubspaceProvider mapState={state => state.componentState} namespace="component"> <Component2 /> </SubspaceProvider> </App>
If we allow regex, perhaps
transparent={true}
could be used as an alias fortransparentTo={[/.*/]}
.
I'm interested in hearing any thoughts on the above, alternative solutions and answers to my questions.
Cheers.
I ran into other issue when using nested react-router-config
's object and <Route/>
's with react-redux-subspace
's subspaced
hoc.
My action namespaces for nested components, like:
- A_1
- B_1
- C_1
- A_2
- B_2
- C_2
NOTE: A_1 and A_2 are using the same subspaced reducer structure. Same goes for other capital letters.
They went berserk for B_1 to something like: namespaceA_2/namespaceA_1/namespaceB_1/ACTION
, while I expected namespaceA_1/namespaceB_1/ACTION
. other routes are also weird.
For now I was forced to make my custom redux middleware to correct action namespaces to solve my issue, but creating one was very painful, I'll try to describe it in a new issue at some point.
We definitely need more power over nested namespaces.
Hi @tlenex,
Something sounds up there. The actions should only every get a namespace of a subspace that they pass through, so I'm not sure how you'd end up with a namespace of namespaceA_2/namespaceA_1/namespaceB_1/ACTION
given the component structure described.
I'm not familiar with react-router-config
. Can you share some code or set up an example in codesandbox.io to show what you are seeing?
Closing due to inactivity. Happy to repoen if there is more discussion to be had.