ioof-holdings/redux-subspace

Discussion: Recommended API for user-defined modules

jcheroske opened this issue · 15 comments

I stumbled upon this the other day:

http://antontelesh.github.io/architecture/2016/03/16/fractal-architecture.html

My main takeaway was that a fractal component should have an api that's something like:

export create = () => ({ view, reducer })

Extending the idea to include the subspace concept, it seems like a possible best practice to build modules might be to use a pattern like:

export const createComponent = id => // return subspaced component

export const createReducer = id => // return subspaced reducer

The great thing here is that the parent component and parent reducer are still in charge of the namespace id, but they know nothing of the namespacing implementation. In some ways, this is like a duck for fractal components. Thoughts?

@jcheroske I wish you could see the library we are looking to open source soon. It sounds a lot like what you're asking for.

I'll add some more details of the API tonight.

That's exciting! I'm very curious now.

Tying together your comment about scoped parent->child actions with this thread, I think that a module should export the action creators that are meant to be called from outside the module as another creator function. Something like:

const createReset = id => // return scoped action creator

The API for a given module then becomes essentially a function that returns the reducer, component, and action creators, all of them scoped.

So I had a better read of the article, and our solution is a little bit more reacty and theoretical than theirs.

Imagine you have a redux connected component

const MyComponent = ({ value, setValue } ) => <button onClick={setValue}>{value}</button>

const mapStateToProps = (state) => ({ value: state })
const mapDispatchToProps = (dispatch) => ({ setValue: () => dispatch(setValue("something else") })

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

And you have a reducer for this component

export default (state = "test", action) => {
    return action.type == SET_VALUE ? action.value : state
}

We have a HOC that lets you go

import reducer from './reducer'
import MyComponent from './MyComponent'

export default withReducer(reducer, "value")(MyComponent)

Then in the parent you just mount it, and it injects a reducer with key value and namespaced to "value" and wraps the component in a subspace that maps to state.value and is namespaced with "value".

import MyComponent from 'my-component' // we generally have them as seperate npm packages

const Parent = () => <MyComponent />

And this works fine, but obviously there is an issue here for reusability. What if we wanted 2 instances of it on the same page? What if another component also uses "value" as it identifier?

No fear... The export also has a function to create unique instances of the component

import MyComponent from 'my-component'

const MyComponent1 = MyComponent.createInstance("value1")
const MyComponent2 = MyComponent.createInstance("value2")

const Parent = () => (
    <div>
        <MyComponent1 />
        <MyComponent2 />
    <div>

And that's the gist of it.

That's amazing! I've always wired up my reducers manually. Seeing this is really starting to change my thinking. I keep coming back to this issue of how to control child components from the parent. The way your code looks, the children are so encapsulated, and reveal nothing about their redux nature, that exposing actions on the children almost seems a shame. Treating them as nothing but ordinary react components seems like the ideal.

Continuing our conversation on reseting a child that we started on #30, if you didn't have the ability to dispatch actions on the children, but only had normal parent->child patterns available (http://andrewhfarmer.com/component-communication/ & gaearon's comment at https://www.bountysource.com/issues/30234335-use-hoist-non-react-methods-instead-of-hoist-non-react-statics), what strategy would you use to reset a child from a parent's saga? You're in redux side-effect land, firing actions left and right. What's the clean way to manipulate that child?

@mpeyper, any chance that lib could be open sourced?

@jcheroske, a very good chance... We literally just need to name it (the nature and scope of the library changed a lot from the original proof of concept, but unlike subspace, a name did not naturally emerge).

If you can think of a suitable name from looking at the API above, I'd love to hear it! :)

I like the namerefract for these redux namespace libs, but it's taken on npm, albeit by a lib that only got 124 dl last month. Perhaps you could convince the maintainer to give up the name?

This HOC is really more of a reducer injector and remover. reject? Lolz.

Now a package that would do action and state namespacing on Components, reducers, sagas, epics and logics, as well as injection and removal of reducers, sagas, epics and logics, would be a thing of beauty. To provide all the tools, as well as best practices, for creating fractal components, such that the end result would be a totally encapsulated Component with a normal React API would be a most welcome addition to the redux ecosystem.

npm dl counts are not a reliable indicator or use. Every time you push a new version you get a surge of downloads from all the various mirrors, and if anyone uses one of the mirrors or an internal npm registry that caches (like we do) it lowers the true download count. I can guarantee redux-subspace get more downloads a month than npm downloads as there would be dozens, even hundreds, a day just from IOOF.

After #18 is done, redux-subspace will be able to do Components, reducers, thunks and sagas. I plan to investigate redux-promise and redux-observable next as I believe these are the next most widely used side effect libraries. redux-logic is now on my radar so I will keep an eye on it as a future candidate for support.

You are also welcome to submit PRs for any middleware (or feature) you want. The new package structure proposed in #18 should make having other middleware support a little easier too (form within this repo or others).

Would you consider adding the withReducer HOC to redux-subspace?

When using this lib with sagas, what's the procedure? Do I install the saga once and then it will run for all instances of the subspaced component, or does the saga get installed with every instantiation of the component?

withReducer relies on a dynamic reducer solution that currently live within the same package as the HOC. I'd sooner it come under the subspace banner, as it relies quite heavily on redux-subsapce too, but as a new package (from the #18 proposition). If the dynamic reducers solution part of it was redux-dynamic-subspace (for example), then I would be comfortable to include withReducer in react-redux-subspace.

I might suggest this to our group and see what they think.

Curious if you wouldn't mind publishing the code for your reducer-injector gizmo? We need something like that badly!

Hi @jcheroske, we're going to look at publishing it in a separate library very soon (coming days). Stay tuned.

That is fantastic news! Thank you for all your hard work.

I'm going to close this off now as the discussion hasn't been progressing and I was only really keeping it open to remind myself that redux-dynamic-reducer hadn't been open sourced yet... but it has now.