ioof-holdings/redux-subspace

Add optional scope parameter when namespacing

mpeyper opened this issue · 2 comments

This is going to get a bit confusing to explain, but I'll do my best.

I've run into an issue where I want to add a namespaced reducer for a subspaced component, but the reducer will be a sibling of parent component's reducer, instead of a child.

Component structure:

  • App
    • Parent (namespace: 'parent')
      • Child (namespace: 'child')

Reducer structure:

  • Root
    • Parent (namspace: 'parent')
    • Child (namespace: 'child')

The reason why the reducer is a sibling and not being combined into the parent's reducer is because the reducers are being added dynamically using the `replaceReducer' which, so far, I have only been able to get reducers to be added to the top level of the store.

The problem is, when an action (e.g. 'SET_VALUE') is dispatched from the child, as it breaks out of each subspace, the namespace gets prepended so the final action type is 'parent/child/SET_VALUE' but the action is ignore by both reducers:

  • The parent reducer will successfully remove the 'parent' namespace but none of it's reducers are listening for the resulting action type ('child/SET_VALUE').
  • The child reducer is looking for an action that starts the 'child\' so it won't pass on an action that starts with 'parent/child'.

I propose the namespacing functionality be expanded to include an scope parameter to apply the namespace to. If passed undefined, if will fallback to the default behaviour of inferring the scope from the nesting hierarchy. I also propose that the same functionality be available anywhere a namespace can be provided (namspace, subspaced, SubspaceProvider)

It could be implemented as:

  1. a seperate optional parameter
    • namespace(reducer, 'child', 'parent')
    • subspaced(mapState, 'child', 'parent')(Component)
    • <SubspaceProvider mapState={mapState} namespace='child' scope='parent'>
  2. optionally accepting an object for the `namespace parameter
    • namespace(reducer, { id: 'child', scope: 'parent' })
    • subspaced(mapState, { id: 'child', scope: 'parent' })(Component)
    • <SubspaceProvider mapState={mapState} namespace={{ id: 'child', scope: 'parent' }}>

Another thing to consider what are acceptable values for scope. I think it should accept:

  1. a string
    • most basic way to add a scope
    • namespace(reducer, { id: 'child', scope: 'parent' }) would result in the reducer being namespaced to 'parent/child'
  2. an array
    • can add multiple levels without knowing the 'prefix/...' convention
    • namespace(reducer, { id: 'child', scope: ['other', 'parent'] }) would result in the reducer being namespaced to 'other/parent/child'

To enable this, it may be necessary for the subspaced store to carry around it's current namespace (or scope?) to be accessed by others when needing to identify the current scope.

If there are no objections to this, I will start working on this soon, or if someone else want's to take a crack, let me know and you can have first dibs.

I've been thinking about this more since writing it up and and I'm not sure if it's actually a good thing to add.

There's something nice and simple about the current method of inferring scope from nesting and introducing explicit scope adds a complexity that is probably not necessary in most cases.

I've decided to put this on hold until I have time to think about it more, or explore alternatives to the dynamic reducer solution that is actually causing the issue here.

I've thought about this and all I need is the current namespace of the subspaced store to be queryable on the store, e.g. add a fully qualified namespace value to the store in the redux context.

With that, I can wrap the problematic reducer in another namespaced reducer to handle the missing layers.

This should result in hierarchies as follows:

Component structure:

  • App
    • Parent (namespace: 'parent')
      • Child (namespace: 'child')

Reducer structure:

  • Root
    • Parent (namespace: 'parent')
    • Child (namespace: 'parent/child')