Skip to content

Commit

Permalink
Made interface changes for higher-order store
Browse files Browse the repository at this point in the history
mapState is now optional: default to `(state) => state[namespace]` (from ioof-holdings#32)
  • Loading branch information
mpeyper committed Jul 15, 2017
1 parent a4cb2c5 commit 3060fe9
Show file tree
Hide file tree
Showing 35 changed files with 295 additions and 130 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"lint": "eslint . --ext .js --ext .jsx",
"lint:fix": "eslint . --ext .js --ext .jsx --fix",
"test": "lerna run test",
"test:watch": "lerna run --parallell test:watch"
"test:watch": "lerna run --parallel test:watch"
},
"repository": {
"type": "git",
Expand Down
19 changes: 13 additions & 6 deletions packages/react-redux-subspace/src/components/SubspaceProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
* LICENSE file in the root directory of this source tree.
*/

import { Component, Children } from 'react'
import React, { Children } from 'react'
import PropTypes from 'prop-types'
import { subspace } from 'redux-subspace'

export default class SubspaceProvider extends Component {
class SubspaceProvider extends React.PureComponent {

getChildContext() {
return { store: subspace(this.context.store, this.props.mapState, this.props.namespace) }
const makeSubspaceDecorator = (props) => props.subspaceDecorator || subspace(props.mapState, props.namespace)

return {
store: makeSubspaceDecorator(this.props)(this.context.store)
}
}

render() {
Expand All @@ -22,15 +26,18 @@ export default class SubspaceProvider extends Component {
}

SubspaceProvider.propTypes = {
mapState: PropTypes.func.isRequired,
children: PropTypes.element.isRequired,
namespace: PropTypes.string
mapState: PropTypes.func,
namespace: PropTypes.string,
subspaceDecorator: PropTypes.func,
}

SubspaceProvider.contextTypes = {
store: PropTypes.object
store: PropTypes.object.isRequired
}

SubspaceProvider.childContextTypes = {
store: PropTypes.object
}

export default SubspaceProvider
34 changes: 21 additions & 13 deletions packages/react-redux-subspace/src/components/subspaced.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,31 @@
*/

import React from 'react'
import { subspace } from 'redux-subspace'
import SubspaceProvider from './SubspaceProvider'

export default (mapState, namespace = undefined) => (WrappedComponent) => {
const subspaced = (mapState, namespace) => {

const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component'
const subspaceDecorator = subspace(mapState, namespace)

const displayName = `Subspaced(${wrappedComponentName})`
return (WrappedComponent) => {

const SubspacedComponent = (props) => (
<SubspaceProvider mapState={mapState} namespace={namespace}>
<WrappedComponent {...props} />
</SubspaceProvider>
)
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component'

SubspacedComponent.displayName = displayName
const displayName = `Subspaced(${wrappedComponentName})`

return SubspacedComponent
}
const SubspacedComponent = (props) => (
<SubspaceProvider subspaceDecorator={subspaceDecorator}>
<WrappedComponent {...props} />
</SubspaceProvider>
)

SubspacedComponent.displayName = displayName

return SubspacedComponent
}
}

export default subspaced
14 changes: 9 additions & 5 deletions packages/react-redux-subspace/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ interface ComponentDecorator {
}

export interface Subspaced {
<TParentState, TRootState, TSubState>(mapState: MapState<TParentState, TRootState, TSubState>, namespace?: string): ComponentDecorator;
<TParentState, TSubState>(mapState: MapState<TParentState, any, TSubState>): ComponentDecorator;
<TParentState, TSubState>(mapState: MapState<TParentState, any, TSubState>, namespace: string): ComponentDecorator;
<TParentState, TRootState, TSubState>(mapState: MapState<TParentState, TRootState, TSubState>): ComponentDecorator;
<TParentState, TRootState, TSubState>(mapState: MapState<TParentState, TRootState, TSubState>, namespace: string): ComponentDecorator;
(namespace: string): ComponentDecorator;
}

export const subspaced: Subspaced
export const subspaced: Subspaced;

export interface SubspaceProviderProps<TParentState, TRootState, TSubState> {
mapState: MapState<TParentState, TRootState, TSubState>;
children: React.ReactNode;
mapState?: MapState<TParentState, TRootState, TSubState>;
namespace?: string;
children?: React.ReactNode;
}

export class SubspaceProvider<TParentState, TRootState, TSubState> extends React.Component<SubspaceProviderProps<TParentState, TRootState, TSubState>> { }
export class SubspaceProvider<TParentState, TRootState, TSubState> extends React.Component<SubspaceProviderProps<TParentState, TRootState, TSubState>> { }
22 changes: 6 additions & 16 deletions packages/react-redux-subspace/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,9 @@
* LICENSE file in the root directory of this source tree.
*/

import { asGlobal, GlobalActions } from './actions/GlobalActions'
import namespaced from './reducers/namespaced'
import subspaced from './components/subspaced'
import SubspaceProvider from './components/SubspaceProvider'
import withStore from './sagas/withStore'
import subspacedSagas from './sagas/subspacedSagas'

export {
asGlobal,
GlobalActions,
namespaced,
subspaced,
SubspaceProvider,
withStore,
subspacedSagas
}
export { asGlobal, GlobalActions } from './actions/GlobalActions'
export { default as namespaced } from './reducers/namespaced'
export { default as subspaced } from './components/subspaced'
export { default as SubspaceProvider } from './components/SubspaceProvider'
export { default as withStore } from './sagas/withStore'
export { default as subspacedSagas } from './sagas/subspacedSagas'
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,27 @@ describe('SubspaceProvider Tests', () => {
expect(testComponent.html()).to.equal("<p>expected</p>")
})

it('should render child component using namespace for substate', () => {
let state = {
subState: {
value: "expected"
},
value: "wrong"
}

let mockStore = configureStore()(state)

let testComponent = render(
<Provider store={mockStore}>
<SubspaceProvider namespace="subState">
<TestComponent />
</SubspaceProvider>
</Provider>
)

expect(testComponent.html()).to.equal("<p>expected</p>")
})

it('should render nested child component with substate', () => {
let state = {
subState: {
Expand Down Expand Up @@ -110,4 +131,4 @@ describe('SubspaceProvider Tests', () => {

expect(testComponent.html()).to.equal("<p>expected 1 - expected 2</p>")
})
})
})
26 changes: 25 additions & 1 deletion packages/react-redux-subspace/test/components/subspaced-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ describe('subspaced Tests', () => {
expect(testComponent.html()).to.equal("<p>expected</p>")
})

it('should render subspaced using namespace for substate', () => {
const TestComponent = connect(state => { return { value: state.value } })(props => (
<p>{props.value}</p>
))
const SubspacedComponent = subspaced("subState")(TestComponent)

let state = {
subState: {
value: "expected"
},
value: "wrong"
}

let mockStore = configureStore()(state)

let testComponent = render(
<Provider store={mockStore}>
<SubspacedComponent />
</Provider>
)

expect(testComponent.html()).to.equal("<p>expected</p>")
})

it('should render subspaced component with props', () => {
const TestComponent = connect(state => { return { value: state.value } })(props => (
<p>{props.value} - {props.otherValue}</p>
Expand Down Expand Up @@ -115,4 +139,4 @@ describe('subspaced Tests', () => {

expect(SubspacedComponent.displayName).to.equal("Subspaced(Component)")
})
})
})
2 changes: 1 addition & 1 deletion packages/react-redux-subspace/test/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ var chai = require("chai")
var sinonChai = require("sinon-chai")
global.expect = chai.expect
global.assert = chai.assert
chai.use(sinonChai)
chai.use(sinonChai)
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ describe('TypeScript definitions', function () {
tt.compile(path.join(__dirname, 'definitions', filename), options, done)
}).timeout(5000)
});
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,15 @@ const TestComponent = () => {
)
}

const TestNamepsacedComponent = () => {
const TestNamespacedComponent = () => {
return (
<SubspaceProvider namespace="testNamespace">
<p>test</p>
</SubspaceProvider>
)
}

const TestSubspacedComponent = () => {
return (
<SubspaceProvider mapState={(state: ParentState) => state.child} namespace="testNamespace">
<p>test</p>
Expand All @@ -45,10 +53,10 @@ const TestComponentWithRootState = () => {
)
}

const TestNamepsacedComponentWithRootState = () => {
const TestSubspacedComponentWithRootState = () => {
return (
<SubspaceProvider mapState={(state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent })} namespace="testNamespace">
<p>test</p>
</SubspaceProvider>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,17 @@ class StandardComponent extends React.Component<TestProps> {

const StatelessComponent: React.StatelessComponent<TestProps> = (props) => <p>props.value</p>

const SubspacedStandardComponent = subspaced((state: ParentState) => state.child)(StandardComponent)
const NamespacedStandardComponent = subspaced((state: ParentState) => state.child, "testNamespace")(StandardComponent)
const SubspacedStatelessComponent = subspaced((state: ParentState) => state.child)(StatelessComponent)
const NamespacedStatelessComponent = subspaced((state: ParentState) => state.child, "testNamespace")(StatelessComponent)

const SubspacedStandardComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }))(StandardComponent)
const NamespacedStandardComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }), "testNamespace")(StandardComponent)
const SubspacedStatelessComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }))(StatelessComponent)
const NamespacedStatelessComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }), "testNamespace")(StatelessComponent)
const SubStatetandardComponent = subspaced((state: ParentState) => state.child)(StandardComponent)
const NamespacedStandardComponent = subspaced("testNamespace")(StandardComponent)
const SubspacedStandardComponent = subspaced((state: ParentState) => state.child, "testNamespace")(StandardComponent)
const SubStateStatelessComponent = subspaced((state: ParentState) => state.child)(StatelessComponent)
const NamespacedStatelessComponent = subspaced("testNamespace")(StatelessComponent)
const SubspacedStatelessComponent = subspaced((state: ParentState) => state.child, "testNamespace")(StatelessComponent)

const SubStateStandardComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }))(StandardComponent)
const SubspacedStandardComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }), "testNamespace")(StandardComponent)
const SubStateStatelessComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }))(StatelessComponent)
const SubspacedStatelessComponentWithRoot = subspaced((state: ParentState, rootState: RootState) => ({ ...state.child, ...rootState.parent }), "testNamespace")(StatelessComponent)

const Rendered: React.StatelessComponent<void> = () => {
return (
Expand All @@ -50,10 +52,10 @@ const Rendered: React.StatelessComponent<void> = () => {
<NamespacedStandardComponent value="test" />
<SubspacedStatelessComponent value="test" />
<NamespacedStatelessComponent value="test" />
<SubStateStandardComponentWithRoot value="test" />
<SubspacedStandardComponentWithRoot value="test" />
<NamespacedStandardComponentWithRoot value="test" />
<SubStateStatelessComponentWithRoot value="test" />
<SubspacedStatelessComponentWithRoot value="test" />
<NamespacedStatelessComponentWithRoot value="test" />
</div>
)
}
}
9 changes: 6 additions & 3 deletions packages/redux-subspace-saga/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export interface ProvideStore {
export const provideStore: ProvideStore;

export interface Subspaced {
<TParentState, TSubState>(mapState: MapState<TParentState, any, TSubState>, namespace?: string): SagaDecorator;
<TParentState, TRootState, TSubState>(mapState: MapState<TParentState, TRootState, TSubState>, namespace?: string): SagaDecorator;
<TParentState, TSubState>(mapState: MapState<TParentState, any, TSubState>): SagaDecorator;
<TParentState, TSubState>(mapState: MapState<TParentState, any, TSubState>, namespace: string): SagaDecorator;
<TParentState, TRootState, TSubState>(mapState: MapState<TParentState, TRootState, TSubState>): SagaDecorator;
<TParentState, TRootState, TSubState>(mapState: MapState<TParentState, TRootState, TSubState>, namespace: string): SagaDecorator;
(namespace: string): SagaDecorator;
}

export const subspaced: Subspaced;
export const subspaced: Subspaced;
39 changes: 22 additions & 17 deletions packages/redux-subspace-saga/src/sagas/subspaced.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,32 @@ const emitter = () => {
}
}

const subspaced = (mapState, namespace) => (saga) => {
return function* wrappedSaga() {
const parentStore = yield getContext('store')
const subspaced = (mapState, namespace) => {

const sagaEmitter = emitter()

const store = {
...subspace(parentStore, mapState, namespace),
subscribe: sagaEmitter.subscribe,
}
const subspaceDecorator = subspace(mapState, namespace)

runSaga(store, provideStore(store)(saga))
return (saga) => {
return function* wrappedSaga() {
const parentStore = yield getContext('store')

yield takeEvery('*', function* (action) {
if (!namespace || GlobalActions.isGlobal(action)) {
sagaEmitter.emit(action)
} else if (action.type && action.type.indexOf(`${namespace}/`) === 0) {
let theAction = {...action, type: action.type.substring(namespace.length + 1)}
sagaEmitter.emit(theAction)
const sagaEmitter = emitter()

const store = {
...subspaceDecorator(parentStore),
subscribe: sagaEmitter.subscribe,
}
})

runSaga(store, provideStore(store)(saga))

yield takeEvery('*', function* (action) {
if (!namespace || GlobalActions.isGlobal(action)) {
sagaEmitter.emit(action)
} else if (action.type && action.type.indexOf(`${namespace}/`) === 0) {
let theAction = {...action, type: action.type.substring(namespace.length + 1)}
sagaEmitter.emit(theAction)
}
})
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ describe('provideStore Tests', () => {
expect(iterator.next({ type: "TEST" }).value).to.deep.equal(put({ type: "USE_TEST" }))
expect(iterator.next().done).to.be.true
})
})
})
Loading

0 comments on commit 3060fe9

Please sign in to comment.